very very close now

This commit is contained in:
pszsh 2026-02-16 16:42:15 -07:00
parent 794f71ccaf
commit ca8682b00a
69 changed files with 25573 additions and 2240 deletions

2
.gitignore vendored
View File

@ -12,8 +12,6 @@ build*
*.png
icons/
*.pdf
*.txt
examples/
build/
build_android/

View File

@ -1,177 +1,156 @@
// File: AD5940_Platform.c
#include "App_Common.h"
#include "hardware/spi.h"
#include "hardware/gpio.h"
#include "rp2040port.h"
// --- Platform Interface Implementation ---
void AD5940_CsClr(void) { gpio_put(PIN_CS, 0); }
void AD5940_CsSet(void) { gpio_put(PIN_CS, 1); }
void AD5940_RstClr(void) { gpio_put(PIN_RST, 0); }
void AD5940_RstSet(void) { gpio_put(PIN_RST, 1); }
void AD5940_Delay10us(uint32_t time) { sleep_us(time * 10); }
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuff, unsigned long length) {
spi_write_read_blocking(spi0, pSendBuffer, pRecvBuff, length);
}
uint32_t AD5940_GetMCUIntFlag(void) {
return (gpio_get(PIN_INT) == 0);
}
uint32_t AD5940_ClrMCUIntFlag(void) {
return 1;
}
uint32_t AD5940_MCUResourceInit(void *pCfg) {
return 0;
}
void AD5940_MCUGpioWrite(uint32_t data) { (void)data; }
uint32_t AD5940_MCUGpioRead(uint32_t pin) { (void)pin; return 0; }
void AD5940_MCUGpioCtrl(uint32_t pin, BoolFlag enable) { (void)pin; (void)enable; }
// --- Hardware Setup ---
void setup_pins(void) {
spi_init(spi0, 16000000);
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_RST);
gpio_set_dir(PIN_RST, GPIO_OUT);
gpio_put(PIN_RST, 1);
gpio_init(PIN_INT);
gpio_set_dir(PIN_INT, GPIO_IN);
gpio_pull_up(PIN_INT);
}
// NOTE: Low-level hardware interface functions are now in rp2040port.c
int32_t AD5940PlatformCfg(void) {
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
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);
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);
fifo_cfg.FIFOEn = bFALSE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
fifo_cfg.FIFOThresh = 6;
AD5940_FIFOCfg(&fifo_cfg);
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg);
fifo_cfg.FIFOEn = bFALSE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
fifo_cfg.FIFOThresh = 6;
AD5940_FIFOCfg(&fifo_cfg);
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg);
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH|AFEINTSRC_CUSTOMINT0, bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH | AFEINTSRC_CUSTOMINT0,
bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
gpio_cfg.FuncSet = GP0_INT;
gpio_cfg.InputEnSet = 0;
gpio_cfg.OutputEnSet = AGPIO_Pin0;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = 0;
AD5940_AGPIOCfg(&gpio_cfg);
gpio_cfg.FuncSet = GP0_INT;
gpio_cfg.InputEnSet = 0;
gpio_cfg.OutputEnSet = AGPIO_Pin0;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = 0;
AD5940_AGPIOCfg(&gpio_cfg);
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
return 0;
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
return 0;
}
void SystemReset(void) {
sleep_ms(100);
AD5940_SoftRst();
sleep_ms(1);
AD5940_Initialize();
AD5940PlatformCfg();
sleep_ms(100);
AD5940_SoftRst();
sleep_ms(1);
AD5940_Initialize();
AD5940PlatformCfg();
}
// --- Helpers ---
uint32_t GetHSTIARtia(uint32_t val) {
switch(val) {
case 200: return HSTIARTIA_200;
case 1000: return HSTIARTIA_1K;
case 5000: return HSTIARTIA_5K;
case 10000: return HSTIARTIA_10K;
case 20000: return HSTIARTIA_20K;
case 40000: return HSTIARTIA_40K;
case 80000: return HSTIARTIA_80K;
case 160000: return HSTIARTIA_160K;
default: return HSTIARTIA_1K;
}
switch (val) {
case 200:
return HSTIARTIA_200;
case 1000:
return HSTIARTIA_1K;
case 5000:
return HSTIARTIA_5K;
case 10000:
return HSTIARTIA_10K;
case 20000:
return HSTIARTIA_20K;
case 40000:
return HSTIARTIA_40K;
case 80000:
return HSTIARTIA_80K;
case 160000:
return HSTIARTIA_160K;
default:
return HSTIARTIA_1K;
}
}
uint32_t GetLPTIARtia(uint32_t val) {
switch(val) {
case 200: return LPTIARTIA_200R;
case 1000: return LPTIARTIA_1K;
case 2000: return LPTIARTIA_2K;
case 3000: return LPTIARTIA_3K;
case 4000: return LPTIARTIA_4K;
case 6000: return LPTIARTIA_6K;
case 8000: return LPTIARTIA_8K;
case 10000: return LPTIARTIA_10K;
case 12000: return LPTIARTIA_12K;
case 16000: return LPTIARTIA_16K;
case 20000: return LPTIARTIA_20K;
case 24000: return LPTIARTIA_24K;
case 30000: return LPTIARTIA_30K;
case 32000: return LPTIARTIA_32K;
case 40000: return LPTIARTIA_40K;
case 48000: return LPTIARTIA_48K;
case 64000: return LPTIARTIA_64K;
case 85000: return LPTIARTIA_85K;
case 96000: return LPTIARTIA_96K;
case 100000: return LPTIARTIA_100K;
case 120000: return LPTIARTIA_120K;
case 128000: return LPTIARTIA_128K;
case 160000: return LPTIARTIA_160K;
case 196000: return LPTIARTIA_196K;
case 256000: return LPTIARTIA_256K;
case 512000: return LPTIARTIA_512K;
default: return LPTIARTIA_1K;
}
switch (val) {
case 200:
return LPTIARTIA_200R;
case 1000:
return LPTIARTIA_1K;
case 2000:
return LPTIARTIA_2K;
case 3000:
return LPTIARTIA_3K;
case 4000:
return LPTIARTIA_4K;
case 6000:
return LPTIARTIA_6K;
case 8000:
return LPTIARTIA_8K;
case 10000:
return LPTIARTIA_10K;
case 12000:
return LPTIARTIA_12K;
case 16000:
return LPTIARTIA_16K;
case 20000:
return LPTIARTIA_20K;
case 24000:
return LPTIARTIA_24K;
case 30000:
return LPTIARTIA_30K;
case 32000:
return LPTIARTIA_32K;
case 40000:
return LPTIARTIA_40K;
case 48000:
return LPTIARTIA_48K;
case 64000:
return LPTIARTIA_64K;
case 85000:
return LPTIARTIA_85K;
case 96000:
return LPTIARTIA_96K;
case 100000:
return LPTIARTIA_100K;
case 120000:
return LPTIARTIA_120K;
case 128000:
return LPTIARTIA_128K;
case 160000:
return LPTIARTIA_160K;
case 196000:
return LPTIARTIA_196K;
case 256000:
return LPTIARTIA_256K;
case 512000:
return LPTIARTIA_512K;
default:
return LPTIARTIA_1K;
}
}
uint32_t GetLPTIARload(uint32_t val) {
if (val <= 200) return LPTIARLOAD_10R;
return LPTIARLOAD_100R;
}
void ImpedanceShowResult(uint32_t *pData, uint32_t DataCount) {
float freq;
fImpPol_Type *pImp = (fImpPol_Type*)pData;
AppIMPCtrl(IMPCTRL_GETFREQ, &freq);
for(int i=0;i<DataCount;i++) {
float mag = pImp[i].Magnitude;
float phase = pImp[i].Phase;
float real = mag * cosf(phase);
float imag = mag * sinf(phase);
printf("DATA,%.2f,%.4f,%.4f,%.4f,%.4f\n", freq, mag, phase * 180.0f / MATH_PI, real, imag);
}
if (val <= 200)
return LPTIARLOAD_10R;
return LPTIARLOAD_100R;
}
void AmperometricShowResult(float *pData, uint32_t DataCount) {
for(int i=0;i<DataCount;i++) {
printf("AMP,%d,%.4f\n", g_AmpIndex++, pData[i]);
}
for (int i = 0; i < DataCount; i++) {
printf("AMP,%d,%.4f\n", g_AmpIndex++, pData[i]);
}
}
void RampShowResult(float *pData, uint32_t DataCount) {
for(int i=0;i<DataCount;i++) {
printf("RAMP,%d,%.4f\n", g_RampIndex++, pData[i]);
}
for (int i = 0; i < DataCount; i++) {
printf("RAMP,%d,%.4f\n", g_RampIndex++, pData[i]);
}
}

View File

@ -2,23 +2,24 @@
#ifndef _APP_COMMON_H_
#define _APP_COMMON_H_
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "ad5940.h"
#include "Impedance.h"
#include "Amperometric.h"
#include "Impedance.h"
#include "RampTest.h"
#include "ad5940.h"
#include "pico/stdlib.h"
#include "rp2040port.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// --- Hardware Definitions ---
#define PIN_MISO 0
#define PIN_CS 1
#define PIN_SCK 2
#define PIN_CS 1
#define PIN_SCK 2
#define PIN_MOSI 3
#define PIN_RST 9
#define PIN_INT 29
#define PIN_RST 9
#define PIN_INT 29
#define APPBUFF_SIZE 512
#define AD5940ERR_STOP 10
@ -29,10 +30,10 @@
// --- Application State Enums ---
typedef enum {
MODE_IDLE,
MODE_IMPEDANCE,
MODE_AMPEROMETRIC,
MODE_RAMP
MODE_IDLE,
MODE_IMPEDANCE,
MODE_AMPEROMETRIC,
MODE_RAMP
} AppMode;
// --- Global Variables ---
@ -76,8 +77,8 @@ void AD5941_InitAll(void);
// From Measurement_Routines.c
void Routine_CalibrateLFO(void);
void Routine_Measure(float freq);
void Routine_Sweep(float start, float end, int steps);
void Routine_Measure(float freq, float bias_mv);
void Routine_Sweep(float start, float end, int steps, float bias_mv);
void Routine_Amperometric(float bias_mv);
void Routine_LSV(float start_mv, float end_mv, int steps, int duration_ms);
void Routine_CalibrateSystem(void);

View File

@ -21,6 +21,7 @@ add_executable(EIS
Measurement_Core.c
Measurement_Routines.c
AD5940_Platform.c
rp2040port.c
)
target_compile_definitions(EIS PRIVATE CHIPSEL_594X)

File diff suppressed because it is too large Load Diff

View File

@ -1,81 +1,111 @@
// File: Impedance.h
/*!
*****************************************************************************
@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"
#include "string.h"
#include <stdio.h>
// Define custom error code for FIFO overflow
#define AD5940ERR_FIFO 20
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[3]; /* Addresses of the Wait commands in the sequence, used to update delays for frequency changes */
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. */
int32_t RealDataCount; /* Actual number of valid points to report to host (used for dummy point filtering) */
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 */
float RtiaVal; /* Rtia value in Ohm (used for 4-wire calculation) */
/* 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 */
float RtiaVal; /* Rtia value in Ohm (used for 4-wire calculation) */
/* 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 ExtRtia; /* External RTIA switch control/value */
uint32_t ExcitBufGain; /* Select from EXCTBUFGAIN_2, EXCTBUFGAIN_0P25 */
uint32_t HsDacGain; /* Select from HSDACGAIN_1, HSDACGAIN_0P2 */
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 ExtRtia; /* External RTIA switch control/value */
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 */
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 */
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 */
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 */
BoolFlag ShortRe0Se0; /* Short RE0 to SE0 */
}AppIMPCfg_Type;
BoolFlag
StopRequired; /* After FIFO is ready, stop the measurement sequence */
uint32_t
FifoDataCount; /* Count how many times impedance have been measured */
BoolFlag ShortRe0Se0; /* Short RE0 to SE0 */
} 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. */
#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);
/* Helper Routines */
AD5940Err AppIMPRtiaCal(void);
void AppIMPCleanup(void);
void AppIMPCalibrateLFO(void);
#endif

View File

@ -19,7 +19,8 @@ void AD5940ImpedanceStructInit(void) {
pImpedanceCfg->RcalVal = 100.0;
pImpedanceCfg->RtiaVal = CalibratedHstiaVal;
pImpedanceCfg->SinFreq = 1000.0;
pImpedanceCfg->FifoThresh = 6;
pImpedanceCfg->FifoThresh =
4; // Changed from 6 to 4 for 2-step measurement (RCAL+Z)
pImpedanceCfg->DacVoltPP = 600.0;
pImpedanceCfg->ExcitBufGain = EXCITBUFGAIN_0P25;
pImpedanceCfg->HsDacGain = HSDACGAIN_0P2;

View File

@ -1,5 +1,9 @@
// File: Measurement_Routines.c
#include "App_Common.h"
#include "Impedance.h"
// Forward declaration if not in header
// int32_t ImpedanceShowResult(uint32_t *pData, uint32_t DataCount);
void Routine_CalibrateLFO(void) {
printf(">> Calibrating LFOSC...\n");
@ -22,40 +26,82 @@ void Routine_CalibrateLFO(void) {
}
}
void Routine_Measure(float freq) {
// Helper to show results (adapted from test/AD5940Main.c)
void ImpedanceShowResult(uint32_t *pData, uint32_t DataCount) {
float freq;
fImpPol_Type *pImp = (fImpPol_Type *)pData;
AppIMPCtrl(IMPCTRL_GETFREQ, &freq);
/*Process data*/
for (int i = 0; i < DataCount; i++) {
printf("DATA,%.2f,0,0,%.6f,%.6f\n", freq, pImp[i].Magnitude, pImp[i].Phase);
}
}
void Routine_Measure(float freq, float bias_mv) {
if (CurrentMode == MODE_AMPEROMETRIC)
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
else if (CurrentMode == MODE_RAMP)
AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IMPEDANCE;
AppIMPCfg_Type *pCfg;
AppIMPGetCfg(&pCfg);
// Cleanup any previous run
AppIMPCleanup();
AD5940ImpedanceStructInit(); // Reload config with current HP settings
// Initialize Structure
AD5940ImpedanceStructInit();
AppIMPCfg_Type *pCfg;
AppIMPGetCfg(&pCfg);
pCfg->WuptClkFreq = LFOSCFreq;
pCfg->SweepCfg.SweepEn = bFALSE;
pCfg->SinFreq = freq;
pCfg->NumOfData = -1;
pCfg->RealDataCount = -1;
pCfg->NumOfData = -1; // Changes based on new logic? Test uses -1 for single
// point? No, test creates sequence.
// Apply Global Settings
pCfg->ShortRe0Se0 = GlobalShortRe0Se0;
pCfg->HstiaRtiaSel = GetHSTIARtia(ConfigHstiaVal);
pCfg->RtiaVal = (float)ConfigHstiaVal;
// pCfg->RtiaVal = (float)ConfigHstiaVal; // removed in new Impedance.c? Check
// struct.
pCfg->BiasVolt = bias_mv;
pCfg->bParaChanged = bTRUE;
if (AppIMPInit(AppBuff, APPBUFF_SIZE) == AD5940ERR_OK) {
AppIMPCtrl(IMPCTRL_START, 0);
} else {
// Initialize App
if (AppIMPInit(AppBuff, APPBUFF_SIZE) != AD5940ERR_OK) {
printf("ERROR: Init Failed\n");
return;
}
// Calibrate RTIA (New implementation requires this)
AppIMPRtiaCal();
// Start Measurement
AppIMPCtrl(IMPCTRL_START, 0);
// Poll for result (Blocking, similar to sweep) - or rely on Interrupt?
// The test code uses polling for sweep. For single measure, we can use the
// ISR method currently in main.c, BUT we need to make sure AppIMPInit didn't
// break anything. Actually, let's keep it consistent with the Sweep logic for
// now to ensure it works.
while (1) {
if (AD5940_GetMCUIntFlag()) {
AD5940_ClrMCUIntFlag();
uint32_t temp = APPBUFF_SIZE;
AppIMPISR(AppBuff, &temp);
ImpedanceShowResult(AppBuff, temp);
break;
}
// Timeout check?
}
}
void Routine_Sweep(float start, float end, int steps) {
// Sweep Implementation matching test/AD5940Main.c
void Routine_Sweep(float start, float end, int steps, float bias_mv) {
if (CurrentMode == MODE_AMPEROMETRIC)
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
else if (CurrentMode == MODE_RAMP)
@ -64,31 +110,67 @@ void Routine_Sweep(float start, float end, int steps) {
AppIMPCfg_Type *pCfg;
AppIMPGetCfg(&pCfg);
AppIMPCleanup();
AD5940ImpedanceStructInit(); // Reload config with current HP settings
// Initial Setup
AD5940ImpedanceStructInit();
pCfg->WuptClkFreq = LFOSCFreq;
pCfg->SweepCfg.SweepEn = bTRUE;
pCfg->SweepCfg.SweepStart = start;
pCfg->SweepCfg.SweepStop = end;
pCfg->SweepCfg.SweepPoints = steps + 1;
pCfg->NumOfData = steps + 1;
pCfg->RealDataCount = steps;
pCfg->SweepCfg.SweepLog = bTRUE;
// Apply Global Settings
pCfg->ShortRe0Se0 = GlobalShortRe0Se0;
pCfg->HstiaRtiaSel = GetHSTIARtia(ConfigHstiaVal);
pCfg->RtiaVal = (float)ConfigHstiaVal;
pCfg->BiasVolt = bias_mv;
pCfg->bParaChanged = bTRUE;
// Calculate Sweep Points
float log_step = pow(end / start, 1.0 / (steps - 1));
float curr_freq = start;
if (AppIMPInit(AppBuff, APPBUFF_SIZE) == AD5940ERR_OK) {
printf("Starting Software Sweep: %.2f Hz to %.2f Hz, %d points\n", start, end,
steps);
for (int i = 0; i < steps; i++) {
// Check for Abort from UART (User wants to stop)
int c = getchar_timeout_us(0);
if (c == 'x') {
printf("Sweep Aborted by User.\n");
AppIMPCleanup();
break;
}
uint32_t temp;
/* Update Frequency */
pCfg->SinFreq = curr_freq;
pCfg->bParaChanged = bTRUE; // Force sequence regeneration
/* Re-Initialize App to apply new frequency (this regenerates sequences) */
AppIMPInit(AppBuff, APPBUFF_SIZE);
/* Calibrate RTIA at this frequency */
AppIMPRtiaCal();
/* Measure Impedance */
AppIMPCtrl(IMPCTRL_START, 0);
} else {
printf("ERROR: Init Failed\n");
/* Wait for result */
int timeout = 100000; // 1s timeout approx
while (timeout > 0) {
if (AD5940_GetMCUIntFlag()) {
AD5940_ClrMCUIntFlag();
temp = APPBUFF_SIZE;
AppIMPISR(AppBuff, &temp);
ImpedanceShowResult(AppBuff, temp);
break; // Done with this point
}
AD5940_Delay10us(1);
timeout--;
}
if (timeout <= 0) {
printf("timeout at freq: %.2f\n", curr_freq);
}
/* Next Frequency */
curr_freq *= log_step;
}
AppIMPCleanup(); // Stop hardware
printf("Sweep Completed.\n");
}
void Routine_Amperometric(float bias_mv) {
@ -143,6 +225,238 @@ void Routine_LSV(float start_mv, float end_mv, int steps, int duration_ms) {
}
}
extern void __AD5940_ReferenceON(void);
// Custom LPTIA Calibration using Internal HSTIA as Reference
// Routing: Excitation -> RE0/CE0, Short SE0
// This allows using HSTIA internal RTIA (e.g. 1k, 5k, etc) as the reference
// Rcal instead of the fixed external 100 Ohm Rcal which causes saturation.
AD5940Err AD5940_LPRtiaCal_UserCustom(LPRTIACal_Type *pCalCfg, void *pResult) {
HSLoopCfg_Type hs_loop;
LPLoopCfg_Type lp_loop;
DSPCfg_Type dsp_cfg;
ADCBaseCfg_Type *pADCBaseCfg;
SWMatrixCfg_Type *pSWCfg;
uint32_t INTCCfg;
BoolFlag bADCClk32MHzMode = bFALSE;
float ExcitVolt;
uint32_t RtiaVal;
uint32_t const LpRtiaTable[] = {
0, 110, 1000, 2000, 3000, 4000, 6000, 8000, 10000,
12000, 16000, 20000, 24000, 30000, 32000, 40000, 48000, 64000,
85000, 96000, 100000, 120000, 128000, 160000, 196000, 256000, 512000};
float const ADCPGAGainTable[] = {1, 1.5, 2, 4, 9};
uint32_t WgAmpWord;
uint32_t ADCPgaGainRtia, ADCPgaGainRcal;
float GainRatio;
iImpCar_Type DftRcal, DftRtia;
if (pCalCfg == NULL)
return AD5940ERR_NULLP;
if (pResult == NULL)
return AD5940ERR_NULLP;
if (pCalCfg->AdcClkFreq > (32000000 * 0.8))
bADCClk32MHzMode = bTRUE;
// Initialize Pointers
pSWCfg = &hs_loop.SWMatCfg;
pADCBaseCfg = &dsp_cfg.ADCBaseCfg;
RtiaVal = LpRtiaTable[pCalCfg->LpTiaRtia];
// Calculate Excitation Voltage
// Note: We are using HSTIA as Reference (Rcal).
// Let's assume we use 1k Internal HSTIA RTIA as Rcal for this calculation to
// aim for 800mVpp
float RcalEffective = 1000.0f; // Approximate
ExcitVolt = 2000 * 0.8 * RcalEffective / RtiaVal;
WgAmpWord = ((uint32_t)(ExcitVolt / 2200 * 2047 * 2) + 1) >> 1;
if (WgAmpWord > 0x7FF)
WgAmpWord = 0x7FF;
// Store original AFECON
// uint32_t reg_afecon = AD5940_ReadReg(REG_AFE_AFECON); // Unused in this
// snippet but good practice to restore if needed
// INTC Config
INTCCfg = AD5940_INTCGetCfg(AFEINTC_1);
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_DFTRDY | AFEINTSRC_SINC2RDY, bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
/* Helper to turn on reference - adapted from AD5940_ReferenceON */
AFERefCfg_Type aferef_cfg;
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);
// __AD5940_ReferenceON();
// DSP Config
AD5940_StructInit(&dsp_cfg, sizeof(dsp_cfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16;
dsp_cfg.ADCFilterCfg.ADCRate =
bADCClk32MHzMode ? ADCRATE_1P6MHZ : ADCRATE_800KHZ;
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = pCalCfg->ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = pCalCfg->ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
memcpy(&dsp_cfg.DftCfg, &pCalCfg->DftCfg, sizeof(pCalCfg->DftCfg));
AD5940_DSPCfgS(&dsp_cfg);
// LP Loop Config
AD5940_StructInit(&lp_loop, sizeof(lp_loop));
lp_loop.LpDacCfg.LpdacSel = LPDAC0;
lp_loop.LpDacCfg.DacData12Bit = 0x800;
lp_loop.LpDacCfg.DacData6Bit = 32;
lp_loop.LpDacCfg.DataRst = bFALSE;
lp_loop.LpDacCfg.LpDacSW =
LPDACSW_VBIAS2LPPA | LPDACSW_VZERO2HSTIA; // Vzero to HSTIA
lp_loop.LpDacCfg.LpDacRef = LPDACREF_2P5;
lp_loop.LpDacCfg.LpDacSrc = LPDACSRC_WG;
lp_loop.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_6BIT;
lp_loop.LpDacCfg.LpDacVzeroMux = LPDACVZERO_12BIT;
lp_loop.LpDacCfg.PowerEn = bTRUE;
lp_loop.LpAmpCfg.LpAmpSel = pCalCfg->LpAmpSel;
lp_loop.LpAmpCfg.LpAmpPwrMod = pCalCfg->LpAmpPwrMod;
lp_loop.LpAmpCfg.LpPaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaRload = LPTIARLOAD_100R;
lp_loop.LpAmpCfg.LpTiaRtia = pCalCfg->LpTiaRtia;
lp_loop.LpAmpCfg.LpTiaRf = LPTIARF_OPEN;
// Use User Routing for LPTIA Switches if needed, but standard should work for
// TIA part
lp_loop.LpAmpCfg.LpTiaSW =
LPTIASW(6) | LPTIASW(8) | (pCalCfg->bWithCtia == bTRUE ? LPTIASW(5) : 0);
AD5940_LPLoopCfgS(&lp_loop);
// HS Loop Config
AD5940_StructInit(&hs_loop, sizeof(hs_loop));
hs_loop.HsTiaCfg.DiodeClose = bFALSE;
hs_loop.HsTiaCfg.HstiaBias = HSTIABIAS_VZERO0; // Bias from LPDAC Vzero
hs_loop.HsTiaCfg.HstiaCtia = 31;
hs_loop.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hs_loop.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hs_loop.HsTiaCfg.HstiaDe1Rload = HSTIADERLOAD_OPEN;
hs_loop.HsTiaCfg.HstiaDe1Rtia = HSTIADERTIA_OPEN;
// Use 1k Internal RTIA as Reference (or match approximate LPTIA range if
// possible, but 1k is a safe start)
hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_1K;
hs_loop.HsDacCfg.ExcitBufGain = 0;
hs_loop.HsDacCfg.HsDacGain = 0;
hs_loop.HsDacCfg.HsDacUpdateRate = 255;
// USER ROUTING: RE0, CE0, SE0(Short)
// Dswitch: Connect SE0
// Pswitch: Connect RE0
// Nswitch: Connect SE0LOAD? Or use SE0 as N?
// User said "utilizing reo and setting it to re0 and ce0 then shorting se0"
// -> SE0 is shorted to RE0? Let's try standard Ladder Routing logic:
// Excitation via CE0/RE0 (P/N switches?) or High Power loop?
// Actually, for LPTIA Cal, we use LPDAC/WG.
// We need to route the current through LPTIA and the Reference.
// Standard Cal uses RCAL0/RCAL1.
// We want to use HSTIA.
// Connect HSTIA to SE0?
// Implementation based on interpretation:
// SWP = RE0 (Excitation P side)
// SWN = SE0 (Excitation N side?) No, SE0 is usually sense.
// SWT = TRTIA (Connects HSTIA to T-Matrix)
hs_loop.SWMatCfg.Dswitch = SWD_SE0;
hs_loop.SWMatCfg.Pswitch = SWP_RE0 | SWP_CE0; // "setting it to re0 and ce0"
hs_loop.SWMatCfg.Nswitch =
SWN_SE0; // "shorting se0" - maybe use SE0 as return?
hs_loop.SWMatCfg.Tswitch =
SWT_TRTIA | SWT_SE0LOAD; // Connect HSTIA and SE0LOAD?
AD5940_HSLoopCfgS(&hs_loop);
// Waveform Generator
hs_loop.WgCfg.WgType = WGTYPE_SIN;
hs_loop.WgCfg.GainCalEn = bTRUE;
hs_loop.WgCfg.OffsetCalEn = bTRUE;
hs_loop.WgCfg.SinCfg.SinFreqWord =
AD5940_WGFreqWordCal(pCalCfg->fFreq, pCalCfg->SysClkFreq);
hs_loop.WgCfg.SinCfg.SinAmplitudeWord = WgAmpWord;
hs_loop.WgCfg.SinCfg.SinOffsetWord = 0;
hs_loop.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&hs_loop);
AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR |
AFECTRL_SINC2NOTCH,
bTRUE);
// 1. Measure Reference (HSTIA Internal RTIA)
// We need to measure voltage across HSTIA.
// Mux P = HSTIA_P, Mux N = HSTIA_N
AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N);
AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR, bTRUE);
AD5940_Delay10us(25);
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
while (AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DFTRDY) == bFALSE)
;
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT | AFECTRL_WG | AFECTRL_ADCPWR,
bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);
DftRcal.Real = AD5940_ReadAfeResult(AFERESULT_DFTREAL);
DftRcal.Image = AD5940_ReadAfeResult(AFERESULT_DFTIMAGE);
// 2. Measure LPTIA
// Mux P = LPTIA_P, Mux N = LPTIA_N
AD5940_ADCMuxCfgS(ADCMUXP_LPTIA0_P, ADCMUXN_LPTIA0_N);
AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR, bTRUE);
AD5940_Delay10us(25);
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
while (AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DFTRDY) == bFALSE)
;
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT | AFECTRL_WG | AFECTRL_ADCPWR,
bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);
DftRtia.Real = AD5940_ReadAfeResult(AFERESULT_DFTREAL);
DftRtia.Image = AD5940_ReadAfeResult(AFERESULT_DFTIMAGE);
// Calculation: Rtia = (V_Rtia / V_Rcal) * Rcal_Value
// Here Rcal_Value is 1000 Ohm (HSTIA 1k)
fImpCar_Type temp;
temp = AD5940_ComplexDivInt(&DftRtia, &DftRcal);
temp.Real *= 1000.0f;
temp.Image *= 1000.0f;
if (pCalCfg->bPolarResult == bFALSE) {
*(fImpCar_Type *)pResult = temp;
} else {
((fImpPol_Type *)pResult)->Magnitude = AD5940_ComplexMag(&temp);
((fImpPol_Type *)pResult)->Phase = AD5940_ComplexPhase(&temp);
}
// Restore INTC
if (INTCCfg & AFEINTSRC_DFTRDY)
;
else
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_DFTRDY, bFALSE);
return AD5940ERR_OK;
}
void Routine_CalibrateSystem(void) {
if (CurrentMode == MODE_AMPEROMETRIC)
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
@ -176,18 +490,20 @@ void Routine_CalibrateSystem(void) {
lprtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
lprtia_cal.ADCSinc2Osr = ADCSINC2OSR_22;
lprtia_cal.bPolarResult = bTRUE;
lprtia_cal.fRcal = 100.0;
lprtia_cal.fRcal = pCfg->RcalVal;
lprtia_cal.LpTiaRtia = GetLPTIARtia(ConfigLptiaVal);
lprtia_cal.LpAmpPwrMod = LPAMPPWR_NORM;
lprtia_cal.bWithCtia = bFALSE;
// Use a low frequency for LPTIA calibration
lprtia_cal.fFreq = 100.0f;
lprtia_cal.DftCfg.DftNum = DFTNUM_2048;
lprtia_cal.DftCfg.DftSrc = DFTSRC_SINC3;
lprtia_cal.DftCfg.HanWinEn = bTRUE;
printf(">> Calibrating LPTIA %d Ohm...\n", ConfigLptiaVal);
if (AD5940_LPRtiaCal(&lprtia_cal, &LpRes) == AD5940ERR_OK) {
printf(">> Calibrating LPTIA %d Ohm using Custom Routing (HSTIA Ref)...\n",
ConfigLptiaVal);
// CALL CUSTOM CALIBRATION FUNCTION
if (AD5940_LPRtiaCal_UserCustom(&lprtia_cal, &LpRes) == AD5940ERR_OK) {
printf("Calibrated LPTIA: Mag = %f Ohm, Phase = %f\n", LpRes.Magnitude,
LpRes.Phase);
CalibratedLptiaVal = LpRes.Magnitude;
@ -207,7 +523,7 @@ void Routine_CalibrateSystem(void) {
memset(&hsrtia_cal, 0, sizeof(hsrtia_cal));
hsrtia_cal.fFreq = 1000.0f;
hsrtia_cal.AdcClkFreq = 16000000.0;
hsrtia_cal.SysClkFreq = 16000000.0;
hsrtia_cal.SysClkFreq = 16000000.0; // Orig was 16M
hsrtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
hsrtia_cal.ADCSinc2Osr = ADCSINC2OSR_22;
hsrtia_cal.bPolarResult = bTRUE;
@ -229,4 +545,8 @@ void Routine_CalibrateSystem(void) {
} else {
printf("HSTIA Calibration Failed\n");
}
// Cleanup to prevent spurious interrupts in main loop
AppIMPCleanup();
CurrentMode = MODE_IDLE;
}

0
diff_core.txt Normal file
View File

0
diff_main.txt Normal file
View File

42
diff_output.txt Normal file
View File

@ -0,0 +1,42 @@
7a8,9
> #define AD5940ERR_STOP 10
>
30c32
< .RcalVal = 100.0, /* Calibration Resistor Value (Ohms) */
---
> .RcalVal = 100.0, /* Calibration Resistor Value (Ohms) */
71a74
> .ShortRe0Se0 = bFALSE,
79,82c82,85
< * @brief Configures the Impedance Sweep parameters.
< * @param start Start Frequency in Hz
< * @param stop Stop Frequency in Hz
< * @param ppd Points Per Decade (Resolution)
---
> * @brief Configures the Impedance Sweep parameters.
> * @param start Start Frequency in Hz
> * @param stop Stop Frequency in Hz
> * @param ppd Points Per Decade (Resolution)
315a319,328
>
> // Handle RE0-SE0 Short (SW11 in LPTIASW0)
> if(AppIMPCfg.ShortRe0Se0 == bTRUE)
> {
> AD5940_SEQGenInsert(SEQ_WR(REG_AFE_LPTIASW0, 0x00000800)); // Close SW11
> }
> else
> {
> AD5940_SEQGenInsert(SEQ_WR(REG_AFE_LPTIASW0, 0x00000000)); // Open all LPTIA switches
> }
625c638
< return AD5940ERR_OK;
---
> return AD5940ERR_STOP; // Return STOP code
755c768,769
< if (AppIMPRegModify(pBuff, &FifoCnt) == AD5940ERR_OK)
---
> int32_t status = AppIMPRegModify(pBuff, &FifoCnt);
> if (status == AD5940ERR_OK)
782a797,798
>
> if (status == AD5940ERR_STOP) return AD5940ERR_STOP;

138
examples/AD5940Main.c.txt Normal file
View File

@ -0,0 +1,138 @@
/*!
*****************************************************************************
@file: AD5940Main.c
@author: Neo Xu
@brief: Used to control specific application and process data.
-----------------------------------------------------------------------------
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_System_Examples
* @{
* @defgroup Battery_Example
* @{
*/
#include "ad5940.h"
#include <stdio.h>
#include "string.h"
#include "math.h"
#include "BATImpedance.h"
#define APPBUFF_SIZE 512
uint32_t AppBuff[APPBUFF_SIZE];
/* It's your choice here how to do with the data. Here is just an example to print them to UART */
int32_t BATShowResult(uint32_t *pData, uint32_t DataCount)
{
fImpCar_Type *pImp = (fImpCar_Type*)pData;
float freq;
AppBATCtrl(BATCTRL_GETFREQ, &freq);
/*Process data*/
for(int i=0;i<DataCount;i++)
{
printf("Freq: %f (real, image) = ,%f , %f ,mOhm \n",freq, pImp[i].Real,pImp[i].Image);
}
return 0;
}
/* Initialize AD5940 basic blocks like clock */
static int32_t AD5940PlatformCfg(void)
{
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
/* Use hardware reset */
AD5940_HWReset();
/* Platform configuration */
AD5940_Initialize();
/* Step1. Configure clock */
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
clk_cfg.SysClkDiv = SYSCLKDIV_1;
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC; //on battery board, there is a 32MHz crystal.
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;//AppBATCfg.FifoThresh; /* DFT result. One pair for RCAL, another for Rz. One DFT result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg); /* Disable to reset FIFO. */
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg); /* Enable FIFO here */
/* Step3. Interrupt controller */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE); /* Enable all interrupt in Interrupt Controller 1, so we can check INTC flags */
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE); /* Interrupt Controller 0 will control GP0 to generate interrupt to MCU */
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Step4: Reconfigure GPIO */
gpio_cfg.FuncSet = GP0_INT|GP2_SYNC;
gpio_cfg.InputEnSet = AGPIO_Pin2;
gpio_cfg.OutputEnSet = AGPIO_Pin0|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 AD5940BATStructInit(void)
{
AppBATCfg_Type *pBATCfg;
AppBATGetCfg(&pBATCfg);
pBATCfg->SeqStartAddr = 0;
pBATCfg->MaxSeqLen = 512;
pBATCfg->RcalVal = 50.0; /* Value of RCAL on EVAL-AD5941BATZ board is 50mOhm */
pBATCfg->ACVoltPP = 300.0f; /* Pk-pk amplitude is 300mV */
pBATCfg->DCVolt = 1200.0f; /* Offset voltage of 1.2V*/
pBATCfg->DftNum = DFTNUM_8192;
pBATCfg->FifoThresh = 2; /* 2 results in FIFO, real and imaginary part. */
pBATCfg->SinFreq = 200; /* Sin wave frequency. THis value has no effect if sweep is enabled */
pBATCfg->SweepCfg.SweepEn = bTRUE; /* Set to bTRUE to enable sweep function */
pBATCfg->SweepCfg.SweepStart = 1.0f; /* Start sweep at 1Hz */
pBATCfg->SweepCfg.SweepStop = 50000.0f; /* Finish sweep at 1000Hz */
pBATCfg->SweepCfg.SweepPoints = 50; /* 100 frequencies in the sweep */
pBATCfg->SweepCfg.SweepLog = bTRUE; /* Set to bTRUE to use LOG scale. Set bFALSE to use linear scale */
}
void AD5940_Main(void)
{
uint32_t temp;
AD5940PlatformCfg();
AD5940BATStructInit(); /* Configure your parameters in this function */
AppBATInit(AppBuff, APPBUFF_SIZE); /* Initialize BAT application. Provide a buffer, which is used to store sequencer commands */
AppBATCtrl(BATCTRL_MRCAL, 0); /* Measur RCAL each point in sweep */
AppBATCtrl(BATCTRL_START, 0);
while(1)
{
/* Check if interrupt flag which will be set when interrupt occurred. */
if(AD5940_GetMCUIntFlag())
{
AD5940_ClrMCUIntFlag(); /* Clear this flag */
temp = APPBUFF_SIZE;
AppBATISR(AppBuff, &temp); /* Deal with it and provide a buffer to store data we got */
AD5940_Delay10us(100000);
BATShowResult(AppBuff, temp); /* Print measurement results over UART */
AD5940_SEQMmrTrig(SEQID_0); /* Trigger next measurement ussing MMR write*/
}
}
}
/**
* @}
* @}
* */

View File

@ -0,0 +1,103 @@
/*!
*****************************************************************************
@file: AD5940_ADCMeanFIFO.c
@author: $Author: nxu2 $
@brief: Use FIFO to read statistic block mean result.
@version: $Revision: 766 $
@date: $Date: 2017-08-21 14:09:35 +0100 (Mon, 21 Aug 2017) $
-----------------------------------------------------------------------------
Copyright (c) 2017-2019 Analog Devices, Inc. All Rights Reserved.
This software is proprietary to Analog Devices, Inc. and its licensors.
By using this software you agree to the terms of the associated
Analog Devices Software License Agreement.
*****************************************************************************/
/** @addtogroup AD5940_Standard_Examples
* @{
@defgroup ADC_MEAN_FIFO_Example
@{
*/
#include "ad5940.h"
#include <stdio.h>
uint32_t ADCBuff[256];
void AD5940_Main(void)
{
ADCBaseCfg_Type adc_base;
ADCFilterCfg_Type adc_filter;
StatCfg_Type stat_cfg;
FIFOCfg_Type fifo_cfg;
/* Use hardware reset */
AD5940_HWReset();
/* Firstly call this function after reset to initialize AFE registers. */
AD5940_Initialize();
/* Configure AFE power mode and bandwidth */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
/* Initialize ADC basic function */
adc_base.ADCMuxP = ADCMUXP_AVDD_2;
adc_base.ADCMuxN = ADCMUXN_VSET1P1;
adc_base.ADCPga = ADCPGA_1;
AD5940_ADCBaseCfgS(&adc_base);
/* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH-->StatisticBlock */
adc_filter.ADCSinc3Osr = ADCSINC3OSR_4;
adc_filter.ADCSinc2Osr = ADCSINC2OSR_1333;
adc_filter.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */
adc_filter.ADCRate = ADCRATE_800KHZ; /* If ADC clock is 32MHz, then set it to ADCRATE_1P6MHZ. Default is 16MHz, use ADCRATE_800KHZ. */
adc_filter.BpNotch = bTRUE; /* SINC2+Notch is one block, when bypass notch filter, we can get fresh data from SINC2 filter. */
adc_filter.BpSinc3 = bFALSE; /* We use SINC3 filter. */
adc_filter.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */
AD5940_ADCFilterCfgS(&adc_filter);
/**
* Statistic block receive data from SINC2+Notch block. Note the diagram in datasheet page 51 PrM.
* The SINC3 can be bypassed optionally. SINC2 cannot be bypassed.
* */
stat_cfg.StatDev = STATDEV_1; /* Not used. */
stat_cfg.StatEnable = bTRUE;
stat_cfg.StatSample = STATSAMPLE_128; /* Sample 128 points and calculate mean. */
AD5940_StatisticCfgS(&stat_cfg);
fifo_cfg.FIFOEn = bTRUE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
fifo_cfg.FIFOSrc = FIFOSRC_MEAN;
fifo_cfg.FIFOThresh = 2;
AD5940_FIFOCfg(&fifo_cfg);
/* Enable all interrupt at Interrupt Controller 1. So we can check the interrupt flag */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE); /* Enable FIFO threshold interrupt. */
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_ClrMCUIntFlag(); /* Clear the MCU interrupt flag which will be set in ISR. */
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bTRUE);
AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE);
while(1)
{
uint32_t FifoCnt;
if(AD5940_GetMCUIntFlag())
{
AD5940_ClrMCUIntFlag(); /* Clear this flag */
if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
{
FifoCnt = AD5940_FIFOGetCnt();
AD5940_FIFORd((uint32_t *)ADCBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
printf("Get %d data, ADC Code[0]:%d\n",FifoCnt, ADCBuff[0]&0xffff);
/*!!!!!NOTE!!!!!*/
/* The mean result already removed 32768. So to calculate the voltage, assume mean result is n, use below equation.
Voltage = n/32768*Vref
*/
}
}
}
}
/**
* @}
* @}
* */

View File

@ -0,0 +1,205 @@
/*!
*****************************************************************************
@file: AD5940_ADCNotchTest.c
@author: Neo Xu
@brief: Notch filter test.
-----------------------------------------------------------------------------
Copyright (c) 2017-2019 Analog Devices, Inc. All Rights Reserved.
This software is proprietary to Analog Devices, Inc. and its licensors.
By using this software you agree to the terms of the associated
Analog Devices Software License Agreement.
*****************************************************************************/
/** @addtogroup AD5940_Standard_Examples
* @{
@defgroup ADC_Notch_Test_Example
@{
*/
#include "ad5940.h"
#include <stdio.h>
#ifdef ADI_DEBUG
#undef ADI_DEBUG
#endif
void ad5940_sequencer_init(uint8_t adc_rate, uint8_t sinc3osr, uint8_t sinc2osr){
FIFOCfg_Type fifo_cfg;
SEQCfg_Type seq_cfg;
/* Step2. Configure FIFO and Sequencer*/
fifo_cfg.FIFOEn = bFALSE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB; /* 4kB for FIFO, The reset 2kB for sequencer */
fifo_cfg.FIFOSrc = FIFOSRC_SINC2NOTCH;
fifo_cfg.FIFOThresh = 1;
AD5940_FIFOCfg(&fifo_cfg); /* Disable to reset FIFO. */
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg); /* Enable FIFO here */
/* Configure sequencer and stop it */
seq_cfg.SeqMemSize = SEQMEMSIZE_2KB;
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
uint32_t WaitClks;
uint8_t dl_60, dl_50, dl=0;
ADCFilterCfg_Type filter;
filter.ADCSinc3Osr = sinc3osr;
filter.ADCSinc2Osr = sinc2osr;
filter.ADCRate = adc_rate;
const uint32_t sinc2osr_table[] = {22,44,89,178,267,533,640,667,800,889,1067,1333,0};
const uint32_t sinc3osr_table[] = {5,4,2,0};
printf("SINC3:OSR%d, SINC2:OSR%d, ", sinc3osr_table[sinc3osr], sinc2osr_table[sinc2osr]);
if(AD5940_Notch50HzAvailable(&filter, &dl_50)){
printf("NOTCH50: DL:%d, %dHz ", dl_50, (uint32_t)((adc_rate==ADCRATE_1P6MHZ?1.6e6:800000.0)/sinc3osr_table[sinc3osr]/sinc2osr_table[sinc2osr]/dl_50 + .5));
dl += dl_50-1;
}
if(AD5940_Notch60HzAvailable(&filter, &dl_60)){
printf("NOTCH60: DL:%d, %dHz ", dl_60, (uint32_t)((adc_rate==ADCRATE_1P6MHZ?1.6e6:800000.0)/sinc3osr_table[sinc3osr]/sinc2osr_table[sinc2osr]/dl_60 + .5));
dl += dl_60-1;
}
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_NOTCH;
clks_cal.ADCRate = adc_rate;
clks_cal.DataCount = 1; /* Sample one data when wakeup */
clks_cal.ADCSinc2Osr = sinc2osr;
clks_cal.ADCSinc3Osr = sinc3osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = adc_rate==ADCRATE_1P6MHZ?.5:1;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
static uint32_t buff[128];
AD5940_SEQGenInit(buff, 128);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin1);
AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*250));
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_SINC2NOTCH, bTRUE); /* Start ADC convert */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks));
//wait for first data ready
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bFALSE); /* Stop ADC convert and DFT */
AD5940_SEQGpioCtrlS(0);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
SEQInfo_Type seqinfo;
AD5940_SEQGenFetchSeq(&seqinfo.pSeqCmd, &seqinfo.SeqLen);
seqinfo.SeqId = SEQID_0;
seqinfo.SeqRamAddr = 0;
seqinfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&seqinfo);
AGPIOCfg_Type gpio_cfg;
gpio_cfg.FuncSet = GP6_SYNC|GP5_SYNC|GP2_TRIG|GP1_SYNC|GP0_INT;
gpio_cfg.InputEnSet = AGPIO_Pin2;
gpio_cfg.OutputEnSet = AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin5|AGPIO_Pin6;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = AGPIO_Pin2;
AD5940_AGPIOCfg(&gpio_cfg);
}
uint32_t ad5940_notch_test(uint8_t adc_rate, uint8_t sinc3osr, uint8_t sinc2osr){
ADCBaseCfg_Type adc_base;
ADCFilterCfg_Type adc_filter;
/* Use hardware reset */
AD5940_HWReset();
/* Firstly call this function after reset to initialize AFE registers. */
AD5940_Initialize();
CLKCfg_Type clk_cfg;
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
clk_cfg.SysClkDiv = adc_rate==ADCRATE_1P6MHZ?SYSCLKDIV_2:SYSCLKDIV_1;
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
clk_cfg.HfOSC32MHzMode = adc_rate==ADCRATE_1P6MHZ?bTRUE:bFALSE;
clk_cfg.HFOSCEn = bTRUE;
clk_cfg.HFXTALEn = bFALSE;
clk_cfg.LFOSCEn = bTRUE;
AD5940_CLKCfg(&clk_cfg);
/* Configure AFE power mode and bandwidth */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
/* Initialize ADC basic function */
adc_base.ADCMuxP = ADCMUXP_VREF1P8DAC;
adc_base.ADCMuxN = ADCMUXN_VSET1P1;
adc_base.ADCPga = ADCPGA_1P5;
AD5940_ADCBaseCfgS(&adc_base);
AD5940_AFECtrlS(AFECTRL_DACREFPWR|AFECTRL_HSDACPWR, bTRUE);
/* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH */
adc_filter.ADCSinc3Osr = sinc3osr;
adc_filter.ADCSinc2Osr = sinc2osr;
adc_filter.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */
adc_filter.ADCRate = adc_rate; /* If ADC clock is 32MHz, then set it to ADCRATE_1P6MHZ. Default is 16MHz, use ADCRATE_800KHZ. */
adc_filter.BpNotch = bFALSE; /* SINC2+Notch is one block, when bypass notch filter, we can get fresh data from SINC2 filter. */
adc_filter.BpSinc3 = bFALSE; /* We use SINC3 filter. */
adc_filter.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */
AD5940_ADCFilterCfgS(&adc_filter);
/* Enable all interrupt at Interrupt Controller 1. So we can check the interrupt flag */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE);
ad5940_sequencer_init(adc_rate, sinc3osr, sinc2osr);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_SEQCtrlS(bTRUE);
AD5940_ClrMCUIntFlag();
AD5940_SEQMmrTrig(SEQID_0);
while(1)
{
{int32_t i = 1000000;while(i--);}
if(AD5940_GetMCUIntFlag())
{
AD5940_ClrMCUIntFlag();
uint32_t fifo_count = AD5940_FIFOGetCnt();
if(fifo_count == 1){
int32_t rd;
AD5940_FIFORd((uint32_t*)&rd, 1);
rd &= 0xffff;
float volt = AD5940_ADCCode2Volt(rd, ADCPGA_1P5, 1.82)+1.11;
volt -= 1.82;
if(volt < 0) volt = -volt;
if(volt > 0.0005){
printf("FAILED:CODE:rd:%d\n", rd);
return 1;
}
printf("PASS\n");
return 0;
}
else{
printf("FAILED:%d\n", fifo_count);
return 1;
}
}
}
}
void AD5940_Main(void)
{
uint32_t failed = 0;
uint8_t sinc3=0, sinc2=0;
uint8_t adc_rate=0;
for(;adc_rate<=1;adc_rate++){
sinc3=0;
for(;sinc3<=ADCSINC3OSR_2;sinc3++){
sinc2=0;
for(;sinc2<=ADCSINC2OSR_1333;sinc2++){
failed |= ad5940_notch_test(adc_rate, sinc3, sinc2);
}
}
}
printf("Test Done with status: %s\n",failed?"FAILED":"SUCCEED");
while(1);
}
/**
* @}
* @}
* */

View File

@ -0,0 +1,110 @@
/*!
*****************************************************************************
@file: AD5940_ADCPolling.c
@author: $Author: nxu2 $
@brief: ADC Polling mode example
@version: $Revision: 766 $
@date: $Date: 2017-08-21 14:09:35 +0100 (Mon, 21 Aug 2017) $
-----------------------------------------------------------------------------
Copyright (c) 2017-2019 Analog Devices, Inc. All Rights Reserved.
This software is proprietary to Analog Devices, Inc. and its licensors.
By using this software you agree to the terms of the associated
Analog Devices Software License Agreement.
*****************************************************************************/
/** @addtogroup AD5940_Standard_Examples
* @{
@defgroup ADC_Polling_Example
@{
*/
#include "ad5940.h"
#include <stdio.h>
#define ADCPGA_GAIN_SEL ADCPGA_1P5
static void AD5940_PGA_Calibration(void){
AD5940Err err;
ADCPGACal_Type pgacal;
pgacal.AdcClkFreq = 16e6;
pgacal.ADCSinc2Osr = ADCSINC2OSR_178;
pgacal.ADCSinc3Osr = ADCSINC3OSR_4;
pgacal.SysClkFreq = 16e6;
pgacal.TimeOut10us = 1000;
pgacal.VRef1p11 = 1.11f;
pgacal.VRef1p82 = 1.82f;
pgacal.PGACalType = PGACALTYPE_OFFSETGAIN;
pgacal.ADCPga = ADCPGA_GAIN_SEL;
err = AD5940_ADCPGACal(&pgacal);
if(err != AD5940ERR_OK){
printf("AD5940 PGA calibration failed.");
}
}
void AD5940_Main(void)
{
ADCBaseCfg_Type adc_base;
ADCFilterCfg_Type adc_filter;
/* Use hardware reset */
AD5940_HWReset();
/* Firstly call this function after reset to initialize AFE registers. */
AD5940_Initialize();
AD5940_PGA_Calibration();
/* Configure AFE power mode and bandwidth */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
/* Initialize ADC basic function */
AD5940_AFECtrlS(AFECTRL_DACREFPWR|AFECTRL_HSDACPWR, bTRUE); //We are going to measure DAC 1.82V reference.
adc_base.ADCMuxP = ADCMUXP_VREF1P8DAC;
adc_base.ADCMuxN = ADCMUXN_VSET1P1;
adc_base.ADCPga = ADCPGA_GAIN_SEL;
AD5940_ADCBaseCfgS(&adc_base);
/* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH */
adc_filter.ADCSinc3Osr = ADCSINC3OSR_4;
adc_filter.ADCSinc2Osr = ADCSINC2OSR_1333;
adc_filter.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */
adc_filter.ADCRate = ADCRATE_800KHZ; /* If ADC clock is 32MHz, then set it to ADCRATE_1P6MHZ. Default is 16MHz, use ADCRATE_800KHZ. */
adc_filter.BpNotch = bTRUE; /* SINC2+Notch is one block, when bypass notch filter, we can get fresh data from SINC2 filter. */
adc_filter.BpSinc3 = bFALSE; /* We use SINC3 filter. */
adc_filter.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */
AD5940_ADCFilterCfgS(&adc_filter);
//AD5940_ADCMuxCfgS(ADCMUXP_AIN2, ADCMUXN_VSET1P1); /* Optionally, you can change ADC MUX with this function */
/* Enable all interrupt at Interrupt Controller 1. So we can check the interrupt flag */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE);
//AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bTRUE);
//AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE);
AD5940_ADCPowerCtrlS(bTRUE);
AD5940_ADCConvtCtrlS(bTRUE);
while(1)
{
uint32_t rd;
if(AD5940_INTCTestFlag(AFEINTC_1,AFEINTSRC_SINC2RDY))
{
static uint32_t count;
AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY);
rd = AD5940_ReadAfeResult(AFERESULT_SINC2);
count ++;
/* ADC Sample rate is 800kSPS. SINC3 OSR is 4, SINC2 OSR is 1333. So the final output data rate is 800kSPS/4/1333 = 150.0375Hz */
if(count == 150) /* Print data @1Hz */
{
count = 0;
float diff_volt = AD5940_ADCCode2Volt(rd, ADCPGA_GAIN_SEL, 1.82);
printf("ADC Code:%d, diff-volt: %.4f, volt:%.4f\n",rd, diff_volt, diff_volt+1.11);
}
}
}
}
/**
* @}
* @}
* */

View File

@ -0,0 +1,83 @@
/*!
*****************************************************************************
@file: AD5940_DFTPolling.c
@author: Neo Xu
@brief: DFT Polling mode 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 "ad5940.h"
#include "stdio.h"
#include "math.h"
/**
* Note: In order to use on-chip DFT engine, WG must be set to SIN wave generator and enable it.
*/
void AD5940_Main(void)
{
DSPCfg_Type dsp_cfg;
WGCfg_Type wg_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);
AD5940_StructInit(&dsp_cfg, sizeof(dsp_cfg));
/* Initialize ADC basic function */
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_VCE0;
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_VSET1P1;
dsp_cfg.ADCBaseCfg.ADCPga = ADCPGA_1;
/* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH */
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = ADCSINC3OSR_4;
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = ADCSINC2OSR_1333;
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_2; /* Don't care about it. Average function is only used for DFT */
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* If ADC clock is 32MHz, then set it to ADCRATE_1P6MHZ. Default is 16MHz, use ADCRATE_800KHZ. */
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE; /* SINC2+Notch is one block, when bypass notch filter, we can get fresh data from SINC2 filter. */
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE; /* We use SINC3 filter. */
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE; /* Enable the SINC2+Notch block. You can also use function AD5940_AFECtrlS */
dsp_cfg.DftCfg.DftNum = DFTNUM_16384;
dsp_cfg.DftCfg.DftSrc = DFTSRC_SINC3;
AD5940_DSPCfgS(&dsp_cfg);
AD5940_StructInit(&wg_cfg, sizeof(wg_cfg));
wg_cfg.WgType = WGTYPE_SIN;
wg_cfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(1000.0, 16000000.0); /* 10kHz */
AD5940_WGCfgS(&wg_cfg);
/* 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|AFECTRL_WG, bTRUE);
AD5940_AFECtrlS(AFECTRL_DFT, bTRUE);
AD5940_ADCConvtCtrlS(bTRUE);
while(1)
{
int32_t real, image;
if(AD5940_INTCTestFlag(AFEINTC_1,AFEINTSRC_DFTRDY))
{
AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);
real = AD5940_ReadAfeResult(AFERESULT_DFTREAL);
if(real&(1<<17))
real |= 0xfffc0000; /* Data is 18bit in two's complement, bit17 is the sign bit */
printf("DFT: %d,", real);
image = AD5940_ReadAfeResult(AFERESULT_DFTIMAGE);
if(image&(1<<17))
image |= 0xfffc0000; /* Data is 18bit in two's complement, bit17 is the sign bit */
printf("%d,", image);
printf("Mag:%f\n", sqrt((float)real*real + (float)image*image));
}
}
}

View File

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

View File

@ -0,0 +1,67 @@
/*!
*****************************************************************************
@file: AD5940_LPDAC.c
@author: Neo Xu
@brief: Low power DAC 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.
*****************************************************************************/
/**
* Note: This example will use LP loop to output voltage on RE0 pin.
* LPDAC reference: internal 2.5V
* LP PA(potentialstat amplifier) is used to buffer voltage from Vbias which connects to 12bit LPDAC output
**/
#include "ad5940.h"
#include "AD5940.h"
#include <stdio.h>
#include "string.h"
void AD5940_Main(void)
{
AFERefCfg_Type ref_cfg;
LPLoopCfg_Type lp_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
/* Initialize everything to zero(false/OFF/PowerDown), only turn on what we need */
AD5940_StructInit(&ref_cfg, sizeof(ref_cfg));
ref_cfg.LpBandgapEn = bTRUE; /* Enable low power bandgap */
ref_cfg.LpRefBufEn = bTRUE; /* Enable the low power reference buffer - 2.5V output */
AD5940_REFCfgS(&ref_cfg); /* Call reference configuration function */
AD5940_StructInit(&lp_cfg, sizeof(lp_cfg)); /* Reset everything to zero(OFF) */
/* Configure what we need below */
lp_cfg.LpDacCfg.LpdacSel = LPDAC0; /* Select LPDAC0. Note LPDAC1 is available on ADuCM355 */
lp_cfg.LpDacCfg.DacData12Bit = 0x800; /* Output midscale voltage (0.2V + 2.4V)/2 = 1.3V */
lp_cfg.LpDacCfg.DacData6Bit = 0; /* 6Bit DAC data */
lp_cfg.LpDacCfg.DataRst =bFALSE; /* Do not keep DATA registers at reset status */
lp_cfg.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lp_cfg.LpDacCfg.LpDacRef = LPDACREF_2P5; /* Select internal 2.5V reference */
lp_cfg.LpDacCfg.LpDacSrc = LPDACSRC_MMR; /* The LPDAC data comes from MMR not WG in this case */
lp_cfg.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; /* Connect Vbias signal to 12Bit LPDAC output */
lp_cfg.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT; /* Connect Vzero signal to 6bit LPDAC output */
lp_cfg.LpDacCfg.PowerEn = bTRUE; /* Power up LPDAC */
lp_cfg.LpAmpCfg.LpAmpSel = LPAMP0;
lp_cfg.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM; /* Set low power amplifiers to normal power mode */
lp_cfg.LpAmpCfg.LpPaPwrEn = bTRUE; /* Enable LP PA(potentialstat amplifier) power */
lp_cfg.LpAmpCfg.LpTiaPwrEn = bTRUE; /* Leave LPTIA power off */
lp_cfg.LpAmpCfg.LpTiaSW = LPTIASW(12)|LPTIASW(13)|LPTIASW(2)|LPTIASW(10)\
|LPTIASW(5)|LPTIASW(9); /* Close these switches to make sure LP PA amplifier is closed loop */
lp_cfg.LpAmpCfg.LpTiaRf = LPTIARF_SHORT;
lp_cfg.LpAmpCfg.LpTiaRtia = LPTIARTIA_200R;
lp_cfg.LpAmpCfg.LpTiaRload = LPTIARLOAD_100R;
AD5940_LPLoopCfgS(&lp_cfg);
while(1);
}

View File

@ -0,0 +1,64 @@
/*!
*****************************************************************************
@file: AD5940_LpLoop.c
@author: Neo Xu
@brief: Example of using low power loop amplifiers and LPDAC.
-----------------------------------------------------------------------------
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.
*****************************************************************************/
/**
* Note: This example will use LP loop to output voltage on RE0 pin.
* LPDAC reference: internal 2.5V
* LP PA(potentialstat amplifier) is used to buffer voltage from Vbias which connects to 12bit LPDAC output
**/
#include "ad5940.h"
#include "AD5940.h"
#include <stdio.h>
#include "string.h"
void AD5940_Main(void)
{
AFERefCfg_Type ref_cfg;
LPLoopCfg_Type lp_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
/* Initialize everything to zero(false/OFF/PowerDown), only turn on what we need */
AD5940_StructInit(&ref_cfg, sizeof(ref_cfg));
ref_cfg.LpBandgapEn = bTRUE; /* Enable low power bandgap */
ref_cfg.LpRefBufEn = bTRUE; /* Enable the low power reference buffer - 2.5V output */
AD5940_REFCfgS(&ref_cfg); /* Call reference configuration function */
AD5940_StructInit(&lp_cfg, sizeof(lp_cfg)); /* Reset everything to zero(OFF) */
/* Configure what we need below */
lp_cfg.LpDacCfg.LpdacSel = LPDAC0;
lp_cfg.LpDacCfg.DacData12Bit = 0x800; /* Output midscale voltage (0.2V + 2.4V)/2 = 1.3V */
lp_cfg.LpDacCfg.DacData6Bit = 0; /* 6Bit DAC data */
lp_cfg.LpDacCfg.DataRst =bFALSE; /* Do not keep DATA registers at reset status */
lp_cfg.LpDacCfg.LpDacSW = LPDACSW_VBIAS2PIN|LPDACSW_VZERO2PIN;//LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lp_cfg.LpDacCfg.LpDacRef = LPDACREF_2P5; /* Select internal 2.5V reference */
lp_cfg.LpDacCfg.LpDacSrc = LPDACSRC_MMR; /* The LPDAC data comes from MMR not WG in this case */
lp_cfg.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; /* Connect Vbias signal to 12Bit LPDAC output */
lp_cfg.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT; /* Connect Vzero signal to 6bit LPDAC output */
lp_cfg.LpDacCfg.PowerEn = bTRUE; /* Power up LPDAC */
lp_cfg.LpAmpCfg.LpAmpSel = LPAMP0;
lp_cfg.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM; /* Set low power amplifiers to normal power mode */
lp_cfg.LpAmpCfg.LpPaPwrEn = bTRUE; /* Enable LP PA(potentialstat amplifier) power */
lp_cfg.LpAmpCfg.LpTiaPwrEn = bFALSE; /* Leave LPTIA power off */
lp_cfg.LpAmpCfg.LpTiaSW = LPTIASW(12)|LPTIASW(15)|LPTIASW(4); /* Close these switches to make sure LP PA amplifier is closed loop */
AD5940_LPLoopCfgS(&lp_cfg);
AD5940_LPDAC0WriteS(0x800,31);
while(1);
}

View File

@ -0,0 +1,82 @@
/*!
*****************************************************************************
@file: AD5940_Reset.c
@author: $Author: nxu2 $
@brief: Demostrate three methods to reset AD5940: External Reset, MMR Reset and Power On Reset.
@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.
*****************************************************************************/
/**
* The example shows three kinds of reset source of AD5940:
* - Hardware/External Reset, this is done via RESET pin. Pull it low to reset AD5940.
* - Software Reset, this is done by write regiter.
* - POR Reset, power on reset is done when the power is firstly applied.
*
* After power up, the program firlsty check reset status, there should be only POR reset flag set.
* Then we perform hardware reset. The reset status should reflect this.
* Note the flag in RSTSTA register is stiky. You can clear it by write 1 to corresponding bit.
* Finally, we perform software reset.
* Program then complete required initialization which should be done whenever there is a reset.
*/
#include "ad5940.h"
#include "AD5940.h"
#include <stdio.h>
#include "string.h"
#include <stdlib.h>
void print_rststa(uint32_t reg)
{
printf("<<<<<<<Reset Status<<<<<\n");
if(reg & 0x01)
printf("POR Reset Happened\n");
if(reg & 0x02)
printf("Hardware/External Reset Happened\n");
if(reg & 0x08)
printf("Software Reset Happened\n");
if((reg&0xb) == 0)
printf("No reset happened\n");
printf(">>>>>>>Reset Status Done>>>>>\n");
}
void AD5940_Main(void)
{
uint32_t temp;
printf("Wait 5 secondes\n");
AD5940_Delay10us(100*5000); /* Delay 5s */
printf("\n1. AD5940 Power ON\n");
temp = AD5940_ReadReg(REG_ALLON_RSTSTA);
print_rststa(temp);
AD5940_WriteReg(REG_ALLON_RSTSTA, 0xf); /* Clear reset status. This register will remain its value until we manually clear it. Reset operation won't reset this register. */
printf("\n2. Perform Hardware reset now!\n");
AD5940_HWReset();
printf("Hardware reset done, status is:\n");
temp = AD5940_ReadReg(REG_ALLON_RSTSTA);
print_rststa(temp);
AD5940_WriteReg(REG_ALLON_RSTSTA, 0xf);
printf("\n3. Perform Software Reset now \n");
AD5940_SoftRst();
printf("Software reset done, status is:\n");
temp = AD5940_ReadReg(REG_ALLON_RSTSTA);
print_rststa(temp);
printf("\nReset Test done \n");
/**
* @note MUST call this function whenever there is reset happened. This function will put AD5940 to right state.
*/
AD5940_Initialize();
AD5940_WriteReg(REG_ALLON_RSTSTA, 0xf); /* Clear reset status register. */
while(1);
}

83
examples/AD5940_SPI.c.txt Normal file
View File

@ -0,0 +1,83 @@
/*!
*****************************************************************************
@file: AD5940_SPI.c
@author: $Author: nxu2 $
@brief: Basic register read/write test 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.
*****************************************************************************/
/**
* This example shows how to read/write AD5940 registers through SPI.
* Use function called AD5940_ReadReg and AD5940_WriteReg.
**/
#include "ad5940.h"
#include "AD5940.h"
#include <stdio.h>
#include "string.h"
#include <stdlib.h>
void AD5940_Main(void)
{
unsigned long temp, i;
/**
* Hardware reset can always put AD5940 to default state.
* We recommend to use hardware reset rather than software reset
* because there are some situations that SPI won't work, for example, AD59840 is in hibernate mode,
* or AD5940 system clock is 32kHz that SPI bus clock should also be limited..
* */
AD5940_HWReset();
/**
* @note MUST call this function whenever there is reset happened. This function will put AD5940 to right state.
* The reset can be software reset or hardware reset or power up reset.
*/
AD5940_Initialize();
/**
* Normal application code starts here.
*/
/**
* Read register test.
*/
temp = AD5940_ReadReg(REG_AFECON_ADIID);
printf("Read ADIID register, got: 0x%04lx\n", temp);
if(temp != AD5940_ADIID)
printf("Read register test failed.\n" );
else
printf("Read register test pass\n");
/**
* Write register test.
* */
srand(0x1234);
i =10000;
while(i--)
{
static unsigned long count;
static unsigned long data;
/* Generate a 32bit random data */
data = rand()&0xffff;
data <<= 16;
data |= rand()&0xffff;
count ++; /* Read write count */
/**
* Register CALDATLOCK is 32-bit width, it's readable and writable.
* We use it to test SPI register access.
*/
AD5940_WriteReg(REG_AFE_CALDATLOCK, data);
temp = AD5940_ReadReg(REG_AFE_CALDATLOCK);
if(temp != data)
printf("Write register test failed @0x%08lx\n", data);
if(!(count%1000))
printf("Read/Write has been done %ld times, latest data is 0x%08lx\n", count, data);
}
printf("SPI read/write test completed");
while(1);
}

View File

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

View File

@ -0,0 +1,225 @@
/*!
*****************************************************************************
@file: AD5940_Temperature.c
@author: Neo Xu
@brief: AD5940 internal temperature sensor example with sequencer support.
-----------------------------------------------------------------------------
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"
/**
* This example shows how to configure temperature sensor and using sequencer to take
* measurements. There is 'chop' function to remove offset errors from circuit, this
* feature is in register REG_AFE_TEMPSENS and is not included in this example. Enable
* this function will have better accuracy.
*/
#define SINC3OSR_SEL ADCSINC3OSR_4
#define SINC2OSR_SEL ADCSINC2OSR_22
#define MEASURE_FREQ 4.0f //4Hz(4SPS)
#define FIFO_THRESHOLD 4 //generate FIFO threshold interrupt every 4 data.
#define BUFF_SIZE 128
//this buffer will be used by sequence generator and used to store result from AD5940
uint32_t buff[BUFF_SIZE];
uint32_t data_count = 0; //the temperature data count in buffer.
/* Initialize AD5940 basic blocks like clock */
static int32_t AD5940PlatformCfg(void){
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
SEQCfg_Type seq_cfg;
AGPIOCfg_Type gpio_cfg;
/* Use hardware reset */
AD5940_HWReset();
/* Platform configuration */
AD5940_Initialize();
/* Step1. Configure clock */
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
clk_cfg.SysClkDiv = SYSCLKDIV_1;
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
clk_cfg.HfOSC32MHzMode = bFALSE;
clk_cfg.HFOSCEn = bTRUE;
clk_cfg.HFXTALEn = bFALSE;
clk_cfg.LFOSCEn = bTRUE;
AD5940_CLKCfg(&clk_cfg);
/* Step2. Configure FIFO and Sequencer*/
fifo_cfg.FIFOEn = bFALSE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB; /* 4kB for FIFO, The reset 2kB for sequencer */
fifo_cfg.FIFOSrc = FIFOSRC_SINC2NOTCH;
fifo_cfg.FIFOThresh = FIFO_THRESHOLD;
AD5940_FIFOCfg(&fifo_cfg); /* Disable to reset FIFO. */
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg); /* Enable FIFO here */
/* Configure sequencer and stop it */
seq_cfg.SeqMemSize = SEQMEMSIZE_2KB;
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Step3. Interrupt controller */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE); /* Enable all interrupt in Interrupt Controller 1, so we can check INTC flags */
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE); /* Interrupt Controller 0 will control GP0 to generate interrupt to MCU */
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Step4: Reconfigure GPIO */
gpio_cfg.FuncSet = GP6_SYNC|GP5_SYNC|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);
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Enable AFE to enter sleep mode. */
return 0;
}
void _ad5940_analog_init(void){
AFERefCfg_Type aferef_cfg;
ADCBaseCfg_Type adc_base;
ADCFilterCfg_Type adc_filter;
//init ad5940 for temperature measurement.
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Init all to disable state */
aferef_cfg.HpBandgapEn = bTRUE;
aferef_cfg.Hp1V1BuffEn = bTRUE;
aferef_cfg.Hp1V8BuffEn = bTRUE; /* The High speed buffers are automatically turned off during hibernate */
aferef_cfg.Disc1V1Cap = bFALSE;
aferef_cfg.Disc1V8Cap = bFALSE;
aferef_cfg.Hp1V8ThemBuff = bFALSE;
aferef_cfg.Hp1V8Ilimit = bFALSE;
aferef_cfg.Lp1V1BuffEn = bFALSE;
aferef_cfg.Lp1V8BuffEn = bFALSE;
/* LP reference control - turn off them to save power*/
aferef_cfg.LpBandgapEn = bFALSE;
aferef_cfg.LpRefBufEn = bFALSE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
/* Initialize ADC basic function */
adc_base.ADCMuxP = ADCMUXP_TEMPP;
adc_base.ADCMuxN = ADCMUXN_TEMPN;
adc_base.ADCPga = ADCPGA_1P5;
AD5940_ADCBaseCfgS(&adc_base);
/* Initialize ADC filters ADCRawData-->SINC3-->SINC2+NOTCH */
adc_filter.ADCSinc3Osr = SINC3OSR_SEL;
adc_filter.ADCSinc2Osr = SINC2OSR_SEL;
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_AFECtrlS(AFECTRL_TEMPSPWR, bTRUE); /* Turn on temperature sensor power */
}
/**
* @brief Init everything we need to measure temperature.
*/
void AD5940_TemperatureInit(void){
uint32_t const *pSeqCmd;
uint32_t seq_len;
SEQInfo_Type seq_info;
WUPTCfg_Type wupt_cfg;
ClksCalInfo_Type clks_cal;
uint32_t WaitClks;
clks_cal.DataType = DATATYPE_SINC2;
clks_cal.DataCount = 1; /* Sample one data when wakeup */
clks_cal.ADCSinc2Osr = SINC2OSR_SEL;
clks_cal.ADCSinc3Osr = SINC3OSR_SEL;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = 1; /* Assume ADC clock is same as system clock */
AD5940_ClksCalculate(&clks_cal, &WaitClks);
_ad5940_analog_init();
//generate sequence to measure temperature sensor output
AD5940_SEQGenInit(buff, BUFF_SIZE); //init sequence generator
AD5940_SEQGenCtrl(bTRUE); //from now on, record all register operations rather than write them to AD5940 through SPI.
AD5940_SEQGpioCtrlS(AGPIO_Pin1); //pull high AGPIO1 so we know the sequencer is running by observing pin status with oscilloscope etc.
AD5940_SEQGenInsert(SEQ_WAIT(16*200)); /* Time for reference settling(if ad5940 is just wake up from hibernate mode) */
AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE); /* Turn ON ADC power */
AD5940_SEQGenInsert(SEQ_WAIT(16*50)); /* wait another 50us for ADC to settle. */
AD5940_AFECtrlS(AFECTRL_TEMPCNV|AFECTRL_ADCCNV, bTRUE); /* Start ADC convert */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks));
AD5940_AFECtrlS(AFECTRL_TEMPCNV|AFECTRL_ADCPWR, bFALSE); /* Stop ADC */
AD5940_SEQGenInsert(SEQ_WAIT(20)); /* Add some delay before put AD5940 to hibernate, needs some clock to move data to FIFO. */
AD5940_SEQGpioCtrlS(0); /* pull low AGPIO so we know end of sequence.*/
AD5940_EnterSleepS();/* Goto hibernate */
AD5940_SEQGenCtrl(bFALSE); /* stop sequence generator */
if(AD5940_SEQGenFetchSeq(&pSeqCmd, &seq_len) != AD5940ERR_OK){
puts("Sequence generator error!");
}
seq_info.pSeqCmd = pSeqCmd;
seq_info.SeqId = SEQID_0; //use SEQ0 to run this sequence
seq_info.SeqLen = seq_len;
seq_info.SeqRamAddr = 0; //place this sequence from start of SRAM.
seq_info.WriteSRAM = bTRUE;// we need to write this sequence to AD5940 SRAM.
AD5940_SEQInfoCfg(&seq_info);
//now configure wakeup timer to trigger above sequence periodically to measure temperature data.
wupt_cfg.WuptEn = bFALSE; // do not start it right now.
wupt_cfg.WuptEndSeq = WUPTENDSEQ_A;
wupt_cfg.WuptOrder[0] = SEQID_0;
wupt_cfg.SeqxSleepTime[SEQID_0] = 4-1;
wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(32e3f/MEASURE_FREQ)-4-1;
AD5940_WUPTCfg(&wupt_cfg);
//enable sequencer
AD5940_SEQCtrlS(bTRUE); //now sequencer is ready to be triggered.
}
void AD5940_TemperatureISR(void){
//process data from AD5940 FIFO.
uint32_t FifoCnt, IntcFlag;
if(AD5940_WakeUp(10) > 10){ /* Wakeup AFE by read register, read 10 times at most */
printf("Failed to wakeup AD5940!\n");
return;
}
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* We need time to read data from FIFO, so, do not let AD5940 goes to hibernate automatically */
IntcFlag = AD5940_INTCGetFlag(AFEINTC_0);
if(IntcFlag&AFEINTSRC_DATAFIFOTHRESH){
FifoCnt = AD5940_FIFOGetCnt();
FifoCnt = FifoCnt>BUFF_SIZE?BUFF_SIZE:FifoCnt;
data_count = FifoCnt;
AD5940_FIFORd(buff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter sleep mode. AFE will stay at active mode until sequencer trigger sleep */
AD5940_EnterSleepS(); //If MCU is too slow, comment this line, otherwise there is chance the sequencer is running at this point.
}
}
void AD5940_PrintResult(void){
for(int i=0; i<data_count; i++){
int32_t data = buff[i]&0xffff;
data -= 0x8000; //data from SINC2 is added 0x8000, while data from register TEMPSENSDAT has no 0x8000 offset.
printf("Result[%d] = %d, %.2f(C)\n", i, data, data/8.13f/1.5f-273.15f);
}
}
void AD5940_Main(void){
AD5940PlatformCfg();
printf("Internal calibration register value:\nGain: 0x%08x\n", AD5940_ReadReg(REG_AFE_ADCGAINDIOTEMPSENS));
printf("Offset: 0x%08x\n", AD5940_ReadReg(REG_AFE_ADCOFFSETEMPSENS1));
AD5940_TemperatureInit();
AD5940_WUPTCtrl(bTRUE); //start wupt, so the sequence will be run periodically.
while(1){
/* Check if interrupt flag which will be set when interrupt occurred. */
if(AD5940_GetMCUIntFlag()){
AD5940_ClrMCUIntFlag(); /* Clear this flag */
AD5940_TemperatureISR();
AD5940_PrintResult();
}
}
}

View File

@ -0,0 +1,204 @@
/*!
*****************************************************************************
@file: AD5940_WGArbitrary.c
@author: $Author: nxu2 $
@brief: Arbitrary Waveform Genertor using sequencer.
@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"
#define SIN_FREQ 200000.0f /* 20kHz */
#define SIN_AMPLITUDE 4095 /* unit is DAC peak to peak code. Maximum is 4095 */
#define SAMPLE_RATE 2000000.0f /* 200kHz */
#define SAMPLE_POINTS 200 /* 100 Points */
#define SYSCLK_FREQ 16000000.0f /* System clock frequency is 16MHz for this example. */
#define APPBUFF_SIZE 1024
static uint32_t AppBuff[APPBUFF_SIZE]; /* We use 2kB SRAM for sequencer in this example, maximum sequence length is 512 */
/**
* Write the method to generate Arbitrary Waveform. You can use expression or looup table.
* In this example, we use sin wave expression.
**/
static uint32_t GetNextDacPoint(float Freq, float SampleRate, uint32_t Index)
{
static uint32_t index;
float fRes;
int32_t iRes;
uint32_t Amplitude = SIN_AMPLITUDE;
if(Amplitude > 4095) Amplitude = 4095;
fRes = Amplitude/2.0f*sin(2*MATH_PI*Freq*Index/SampleRate);
iRes = (int32_t)(fRes) + 0x800;
printf("index:%d, fRes:%f, iRes:%d\n", index++, fRes, iRes);
if(iRes < 0) iRes = 0;
if(iRes > 0xfff) iRes = 0xfff;
return iRes;
}
static AD5940Err BuildSequence(void)
{
AD5940Err error = AD5940ERR_OK;
SEQCfg_Type seqcfg;
SEQInfo_Type seqinfo;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type HpLoopCfg;
AD5940_SEQGenInit(AppBuff, APPBUFF_SIZE);
AD5940_SEQGenCtrl(bTRUE); /* Start sequencer generator here */
/* sequence starts here */
AD5940_SEQGpioCtrlS(AGPIO_Pin1); /* Pull high GPIO to indicate sequencer is running */
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
/* Step1. Init reference system */
AD5940_StructInit(&aferef_cfg, sizeof(aferef_cfg)); /* Disable everything and only enable below functions */
aferef_cfg.HpBandgapEn = bTRUE;
aferef_cfg.Hp1V1BuffEn = bTRUE;
aferef_cfg.Hp1V8BuffEn = bTRUE;
/* LP reference control */
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
AD5940_REFCfgS(&aferef_cfg);
/* Step2: Configure HSLoop: HSDAC, HSTIA, SWMatrix and WG(MMR type) */
AD5940_StructInit(&HpLoopCfg, sizeof(HpLoopCfg));
HpLoopCfg.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2;
HpLoopCfg.HsDacCfg.HsDacGain = HSDACGAIN_1;
HpLoopCfg.HsDacCfg.HsDacUpdateRate = 7;
HpLoopCfg.HsTiaCfg.DiodeClose = bFALSE;
HpLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
HpLoopCfg.HsTiaCfg.HstiaCtia = 16; /* 16pF */
HpLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
HpLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_TODE; /* Connect HSTIA output to DE0 pin */
HpLoopCfg.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_200;
HpLoopCfg.SWMatCfg.Dswitch = SWD_CE0;
HpLoopCfg.SWMatCfg.Pswitch = SWP_CE0;
HpLoopCfg.SWMatCfg.Nswitch = SWN_SE0LOAD;
HpLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA|SWT_SE0LOAD;
HpLoopCfg.WgCfg.WgType = WGTYPE_MMR; /* We use sequencer to update DAC data point by point. */
HpLoopCfg.WgCfg.GainCalEn = bFALSE;
HpLoopCfg.WgCfg.OffsetCalEn = bFALSE;
HpLoopCfg.WgCfg.WgCode = 0x800; /* Init to mid-scale */
AD5940_HSLoopCfgS(&HpLoopCfg);
AD5940_AFECtrlS(AFECTRL_DACREFPWR, bTRUE);
AD5940_AFECtrlS(AFECTRL_EXTBUFPWR|AFECTRL_INAMPPWR|AFECTRL_HSTIAPWR|AFECTRL_HSDACPWR, bTRUE);
AD5940_AFECtrlS(AFECTRL_WG, bTRUE);
AD5940_StructInit(&seqcfg, sizeof(seqcfg)); /* Disable everything */
seqcfg.SeqMemSize = SEQMEMSIZE_2KB; /* 2kB SRAM is used for sequencer, others for data FIFO */
seqcfg.SeqEnable = bTRUE; /* Keep sequencer enabled */
seqcfg.SeqWrTimer = ((uint32_t)(SYSCLK_FREQ/SAMPLE_RATE + 0.5f)-1); /* Run next command after write timer and timer is set to update rate */
if(seqcfg.SeqWrTimer > 255)
return AD5940ERR_PARA;
AD5940_SEQCfg(&seqcfg);
for(uint32_t i=0; i<SAMPLE_POINTS; i++)
{
uint32_t daccode;
daccode = GetNextDacPoint(SIN_FREQ, SAMPLE_RATE, i);
AD5940_WGDACCodeS(daccode);
}
AD5940_SEQGpioCtrlS(0); /* Pull low GPIO to indicate sequencer stopped */
seqcfg.SeqEnable = bFALSE; /* Stop sequencer */
seqcfg.SeqWrTimer = 0; /* Reset write timer to 0 */
AD5940_SEQCfg(&seqcfg);
/* End of sequence */
error = AD5940_SEQGenFetchSeq(&seqinfo.pSeqCmd, &seqinfo.SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator here */
if(error != AD5940ERR_OK)
return error;
if(seqinfo.SeqLen > 512)
return AD5940ERR_SEQLEN;
seqinfo.SeqId = SEQID_0;
seqinfo.SeqRamAddr = 0;
seqinfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&seqinfo);
return AD5940ERR_OK;
}
/* Initialize AD5940 basic blocks like clock */
static int32_t AD5940PlatformCfg(void)
{
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
/* Use hardware reset */
AD5940_HWReset();
/* Platform configuration */
AD5940_Initialize();
/* Step1. Configure clock */
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
clk_cfg.SysClkDiv = SYSCLKDIV_1;
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
clk_cfg.HfOSC32MHzMode = bFALSE;
clk_cfg.HFOSCEn = bTRUE;
clk_cfg.HFXTALEn = bFALSE;
clk_cfg.LFOSCEn = bTRUE;
AD5940_CLKCfg(&clk_cfg);
/* Step2. Configure FIFO and Sequencer*/
fifo_cfg.FIFOEn = bFALSE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB; /* 4kB for FIFO, The reset 2kB for sequencer */
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
fifo_cfg.FIFOThresh = 4;//AppBIACfg.FifoThresh; /* DFT result. One pair for RCAL, another for Rz. One DFT result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg); /* Disable to reset FIFO. */
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg); /* Enable FIFO here */
/* Step3. Interrupt controller */
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ALLINT, bTRUE); /* Enable all interrupt in Interrupt Controller 1, so we can check INTC flags */
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Step4: Reconfigure GPIO */
gpio_cfg.FuncSet = GP2_TRIG|GP1_SYNC|GP0_INT;
gpio_cfg.InputEnSet = AGPIO_Pin2;
gpio_cfg.OutputEnSet = AGPIO_Pin0|AGPIO_Pin1|AGPIO_Pin4|AGPIO_Pin5|AGPIO_Pin6;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = 0;
AD5940_AGPIOCfg(&gpio_cfg);
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
return 0;
}
void AD5940_Main(void)
{
AD5940Err error;
SEQCfg_Type seqcfg;
AD5940PlatformCfg();
AD5940_StructInit(&seqcfg, sizeof(seqcfg));
seqcfg.SeqEnable = bTRUE;
seqcfg.SeqMemSize = SEQMEMSIZE_2KB; /* 2kB SRAM is used for sequencer, others for data FIFO */
AD5940_SEQCfg(&seqcfg); /* Enable Sequencer here. */
error = BuildSequence(); /* Generate sequencer commands and load it to SRAM */
if(error != AD5940ERR_OK)
{
printf("Build Sequence error, errorno:%d. \n", error);
while(1);
}
AD5940_SEQMmrTrig(SEQID_0); /* Trigger sequence0 */
while(1)
{
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ);
AD5940_SEQCtrlS(bTRUE);
AD5940_SEQMmrTrig(SEQID_0);
}
}

View File

@ -0,0 +1,93 @@
/*!
*****************************************************************************
@file: AD5940_WGSin.c
@author: Neo Xu
@brief: Waveform generator(sin wave) example include switch matrix.
-----------------------------------------------------------------------------
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 "AD5940.h"
#include <stdio.h>
#include "string.h"
#define SIN_FREQ 25000 /* 25kHz */
#define SYS_CLOCK_HZ 16000000.0 /* System clock frequency */
void AD5940_Main(void)
{
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type HpLoopCfg;
CLKCfg_Type clk_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
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);
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 */
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
HpLoopCfg.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2;
HpLoopCfg.HsDacCfg.HsDacGain = HSDACGAIN_1;
HpLoopCfg.HsDacCfg.HsDacUpdateRate = 7;
HpLoopCfg.HsTiaCfg.DiodeClose = bFALSE;
HpLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
HpLoopCfg.HsTiaCfg.HstiaCtia = 16; /* 16pF */
HpLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
HpLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_TODE;
HpLoopCfg.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_160K;
HpLoopCfg.SWMatCfg.Dswitch = SWD_CE0;
HpLoopCfg.SWMatCfg.Pswitch = SWP_CE0;
HpLoopCfg.SWMatCfg.Nswitch = SWN_SE0LOAD;
HpLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA|SWT_SE0LOAD;
HpLoopCfg.WgCfg.WgType = WGTYPE_SIN;
HpLoopCfg.WgCfg.GainCalEn = bFALSE;
HpLoopCfg.WgCfg.OffsetCalEn = bFALSE;
HpLoopCfg.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(SIN_FREQ,SYS_CLOCK_HZ);
HpLoopCfg.WgCfg.SinCfg.SinAmplitudeWord = 2047;
HpLoopCfg.WgCfg.SinCfg.SinOffsetWord = 0;
HpLoopCfg.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&HpLoopCfg);
AD5940_AFECtrlS(AFECTRL_DACREFPWR, bTRUE);
AD5940_AFECtrlS(AFECTRL_EXTBUFPWR|AFECTRL_INAMPPWR|AFECTRL_HSTIAPWR|AFECTRL_HSDACPWR, bTRUE);
AD5940_AFECtrlS(AFECTRL_WG, bTRUE);
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
while(1);
}

View File

@ -0,0 +1,120 @@
/*!
*****************************************************************************
@file: AD5940_WGSin_LPDAC.c
@author: $Author: nxu2 $
@brief: Waveform generator(sin wave) example using LPDAC.
@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"
/**
* This example is to generate sin wave on pin CE0 using waveform generator and LPDAC.
* Signal generator simpley generates digital codes. The code can route to both
* HSDAC and LPDAC. So, we can generate sin wave using both DAC.
*
* @note: LPDAC has limited bandwidth, do not use it to generate signal above 300Hz,
* otherwise, you will see significant performance drop.
* The DAC update rate parameter is decided by register HSDACCON.Rate. This also true
* when using LPDAC as data sink.
*/
#define SIN_AMPLITUDE 1100.0 /**< Signal amplitude in mV.*/
#define SIN_FREQ 100.0 /**< 100Hz. Max is 300Hz */
#define WG_CLOCK_HZ 32e3f /**< Waveform generator clock frequency. Equal to system clock. */
void AD5940_Main(void)
{
AFERefCfg_Type aferef_cfg;
CLKCfg_Type clk_cfg;
LPDACCfg_Type lpdac_cfg;
WGCfg_Type WgCfg;
LPAmpCfg_Type lpamp_cfg;
HSDACCfg_Type HsDacCfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
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);
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 */
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
/* Configure LPDAC*/
lpdac_cfg.LpdacSel = LPDAC0;
lpdac_cfg.DataRst = bFALSE;
lpdac_cfg.LpDacSW = LPDACSW_VBIAS2LPPA/*|LPDACSW_VBIAS2PIN*/|LPDACSW_VZERO2LPTIA/*|LPDACSW_VZERO2PIN*/;
lpdac_cfg.LpDacRef = LPDACREF_2P5; /* Use internal 2.5V reference */
lpdac_cfg.LpDacSrc = LPDACSRC_WG; /* Use data from waveform generator */
lpdac_cfg.LpDacVbiasMux = LPDACVBIAS_12BIT;
lpdac_cfg.LpDacVzeroMux = LPDACVZERO_6BIT; /* Use 6bit LPDAC for Vzero */
lpdac_cfg.PowerEn = bTRUE; /* Enable LPDAC */
lpdac_cfg.DacData12Bit = 0; /* Don't care, 12bit DAC data is from WG */
lpdac_cfg.DacData6Bit = 32;
AD5940_LPDACCfgS(&lpdac_cfg);
/* Configure low power amplifiers */
lpamp_cfg.LpAmpSel = LPAMP0;
lpamp_cfg.LpAmpPwrMod = LPAMPPWR_NORM; /* Use normal power mode is enough */
lpamp_cfg.LpPaPwrEn = bTRUE; /* Enable Potential amplifier */
lpamp_cfg.LpTiaPwrEn = bFALSE; /* TIA is not used in this example */
lpamp_cfg.LpTiaRf = LPTIARF_1M;
lpamp_cfg.LpTiaRload = LPTIARLOAD_100R; /* don't care */
lpamp_cfg.LpTiaRtia = LPTIARTIA_1K; /* don't care */
lpamp_cfg.LpTiaSW = 0; /* don't care */
AD5940_LPAMPCfgS(&lpamp_cfg);
HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2;
HsDacCfg.HsDacGain = HSDACGAIN_1;
HsDacCfg.HsDacUpdateRate = 7; /* DAC update rate equals to WG_CLK/HsDacUpdateRate */
AD5940_HSDacCfgS(&HsDacCfg);
/* Configure Waveform Generator */
WgCfg.WgType = WGTYPE_SIN;
WgCfg.GainCalEn = bFALSE;
WgCfg.OffsetCalEn = bFALSE;
WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(SIN_FREQ, WG_CLOCK_HZ);
WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)(SIN_AMPLITUDE/1100.0f*2047);
WgCfg.SinCfg.SinOffsetWord = 0;
WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_WGCfgS(&WgCfg);
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_AUTOSET);
AD5940_AFECtrlS(AFECTRL_WG, bTRUE);
/* Change to 32kHz clock. LPDAC needs 32kHz clock for waveform generator */
AD5940_LPModeEnS(bTRUE); /* Enter LP control mode. The registers are summarized to LPMODECON, so we can control some blocks conveniently */
AD5940_LPModeClkS(LPMODECLK_LFOSC); /* Trigger switching system clock to 32kHz */
AD5940_LPModeCtrlS(LPMODECTRL_NONE); /* Disable all */
AD5940_LPModeCtrlS(LPMODECTRL_GLBBIASZ|LPMODECTRL_GLBBIASP|LPMODECTRL_HPREFPWR|LPMODECTRL_BUFHP1P8V|LPMODECTRL_BUFHP1P1V|LPMODECTRL_HFOSCEN);
while(1);
}

View File

@ -0,0 +1,98 @@
/*!
*****************************************************************************
@file: AD5940_WGTrapezoid.c
@author: $Author: nxu2 $
@brief: Waveform generator example include switch matrix.
@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 "AD5940.h"
#include <stdio.h>
#include "string.h"
#define SIN_FREQ 200000.0 /* 25kHz */
#define SYS_CLOCK_HZ 16000000.0 /* System clock frequency */
void AD5940_Main(void)
{
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type HsloopCfg;
CLKCfg_Type clk_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
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);
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 */
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
HsloopCfg.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2;
HsloopCfg.HsDacCfg.HsDacGain = HSDACGAIN_1;
HsloopCfg.HsDacCfg.HsDacUpdateRate = 7;
HsloopCfg.HsTiaCfg.DiodeClose = bFALSE;
HsloopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
HsloopCfg.HsTiaCfg.HstiaCtia = 16; /* 16pF */
HsloopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
HsloopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_TODE;
HsloopCfg.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_160K;
HsloopCfg.SWMatCfg.Dswitch = SWD_CE0;
HsloopCfg.SWMatCfg.Pswitch = SWP_CE0;
HsloopCfg.SWMatCfg.Nswitch = SWN_SE0LOAD;
HsloopCfg.SWMatCfg.Tswitch = SWT_TRTIA|SWT_SE0LOAD;
HsloopCfg.WgCfg.WgType = WGTYPE_TRAPZ;
HsloopCfg.WgCfg.GainCalEn = bFALSE;
HsloopCfg.WgCfg.OffsetCalEn = bFALSE;
HsloopCfg.WgCfg.TrapzCfg.WGTrapzDCLevel1 = 0x200;
HsloopCfg.WgCfg.TrapzCfg.WGTrapzDCLevel2 = 0xa00;
HsloopCfg.WgCfg.TrapzCfg.WGTrapzDelay1 = 50;
HsloopCfg.WgCfg.TrapzCfg.WGTrapzDelay2 = 100;
HsloopCfg.WgCfg.TrapzCfg.WGTrapzSlope1 = 200;
HsloopCfg.WgCfg.TrapzCfg.WGTrapzSlope2 = 300;
AD5940_HSLoopCfgS(&HsloopCfg);
AD5940_AFECtrlS(AFECTRL_DACREFPWR, bTRUE);
AD5940_AFECtrlS(AFECTRL_EXTBUFPWR|AFECTRL_INAMPPWR|AFECTRL_HSTIAPWR|AFECTRL_HSDACPWR, bTRUE);
AD5940_AFECtrlS(AFECTRL_WG, bTRUE);
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
while(1);
}

489
examples/Amperometric.c.txt Normal file
View File

@ -0,0 +1,489 @@
/*!
*****************************************************************************
@file: Amperometric.c
@author: $Author: mlambe $
@brief: Amperometric measurement.
@version: $Revision: 766 $
@date: $Date: 2018-03-21 14:09:35 +0100 (Wed, 21 Mar 2018) $
-----------------------------------------------------------------------------
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 "Amperometric.h"
/*
Application configuration structure. Specified by user from template.
The variables are usable in this whole application.
It includes basic configuration for sequencer generator and application related parameters
*/
AppAMPCfg_Type AppAMPCfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.FifoThresh = 5, /* Number of points for FIFO */
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.AmpODR = 1.0, /* Sample time in seconds. I.e. every 5 seconds make a measurement */
.NumOfData = -1,
.RcalVal = 10000.0, /* RCAL = 10kOhm */
.PwrMod = AFEPWR_LP,
.AMPInited = bFALSE,
.StopRequired = bFALSE,
/* LPTIA Configure */
.ExtRtia = bFALSE, /* Set to true if using external RTIA */
.LptiaRtiaSel = LPTIARTIA_4K, /* COnfigure RTIA */
.LpTiaRf = LPTIARF_1M, /* Configure LPF resistor */
.LpTiaRl = LPTIARLOAD_100R,
.ReDoRtiaCal = bTRUE,
.RtiaCalValue = 0,
.ExtRtiaVal = 0,
/*LPDAC Configure */
.Vzero = 1100, /* Sets voltage on SE0 and LPTIA */
.SensorBias = 500, /* Sets voltage between RE0 and SE0 */
/* ADC Configure*/
.ADCPgaGain = ADCPGA_1P5,
.ADCSinc3Osr = ADCSINC3OSR_4,
.ADCSinc2Osr = ADCSINC2OSR_22,
.DataFifoSrc = FIFOSRC_SINC2NOTCH,
.ADCRefVolt = 1.8162, /* Measure voltage on ADCRefVolt pin and enter here*/
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
AD5940Err AppAMPGetCfg(void *pCfg)
{
if(pCfg){
*(AppAMPCfg_Type**)pCfg = &AppAMPCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
AD5940Err AppAMPCtrl(int32_t AmpCtrl, void *pPara)
{
switch (AmpCtrl)
{
case AMPCTRL_START:
{
WUPTCfg_Type wupt_cfg;
AD5940_ReadReg(REG_AFE_ADCDAT); /* Any SPI Operation can wakeup AFE */
if(AppAMPCfg.AMPInited == bFALSE)
return AD5940ERR_APPERROR;
/* Start it */
wupt_cfg.WuptEn = bTRUE;
wupt_cfg.WuptEndSeq = WUPTENDSEQ_A;
wupt_cfg.WuptOrder[0] = SEQID_0;
wupt_cfg.SeqxSleepTime[SEQID_0] = 4-1;
wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(AppAMPCfg.WuptClkFreq*AppAMPCfg.AmpODR)-4-1;
AD5940_WUPTCfg(&wupt_cfg);
AppAMPCfg.FifoDataCount = 0; /* restart */
break;
}
case AMPCTRL_STOPNOW:
{
AD5940_ReadReg(REG_AFE_ADCDAT); /* Any SPI Operation can wakeup AFE */
/* 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 AMPCTRL_STOPSYNC:
{
AppAMPCfg.StopRequired = bTRUE;
break;
}
case AMPCTRL_SHUTDOWN:
{
AppAMPCtrl(AMPCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
/* Turn off LPloop related blocks which are not controlled automatically by sleep 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;
}
/* Generate init sequence */
static AD5940Err AppAMPSeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
LPLoopCfg_Type lp_loop;
DSPCfg_Type dsp_cfg;
SWMatrixCfg_Type sw_cfg;
/* 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 = bTRUE;
aferef_cfg.Lp1V8BuffEn = bTRUE;
/* LP reference control - turn off them to save power*/
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
lp_loop.LpDacCfg.LpdacSel = LPDAC0;
lp_loop.LpDacCfg.LpDacSrc = LPDACSRC_MMR;
lp_loop.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lp_loop.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT;
lp_loop.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT;
lp_loop.LpDacCfg.LpDacRef = LPDACREF_2P5;
lp_loop.LpDacCfg.DataRst = bFALSE;
lp_loop.LpDacCfg.PowerEn = bTRUE;
lp_loop.LpDacCfg.DacData6Bit = (uint32_t)((AppAMPCfg.Vzero-200)/DAC6BITVOLT_1LSB);
lp_loop.LpDacCfg.DacData12Bit =(int32_t)((AppAMPCfg.SensorBias)/DAC12BITVOLT_1LSB) + lp_loop.LpDacCfg.DacData6Bit*64;
if(lp_loop.LpDacCfg.DacData12Bit>lp_loop.LpDacCfg.DacData6Bit*64)
lp_loop.LpDacCfg.DacData12Bit--;
lp_loop.LpAmpCfg.LpAmpSel = LPAMP0;
lp_loop.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM;
lp_loop.LpAmpCfg.LpPaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaRf = AppAMPCfg.LpTiaRf;
lp_loop.LpAmpCfg.LpTiaRload = AppAMPCfg.LpTiaRl;
if(AppAMPCfg.ExtRtia == bTRUE)
{
lp_loop.LpAmpCfg.LpTiaRtia = LPTIARTIA_OPEN;
lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(9)|LPTIASW(2)|LPTIASW(4)|LPTIASW(5)|LPTIASW(12)|LPTIASW(13);
}else
{
lp_loop.LpAmpCfg.LpTiaRtia = AppAMPCfg.LptiaRtiaSel;
lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(5)|LPTIASW(2)|LPTIASW(4)|LPTIASW(12)|LPTIASW(13);
}
AD5940_LPLoopCfgS(&lp_loop);
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_VZERO0;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_AIN4;
dsp_cfg.ADCBaseCfg.ADCPga = AppAMPCfg.ADCPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
memset(&dsp_cfg.DftCfg, 0, sizeof(dsp_cfg.DftCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care because it's disabled */
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* Tell filter block clock rate of ADC*/
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppAMPCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppAMPCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.BpNotch = bFALSE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); /* Don't care about Statistic */
AD5940_DSPCfgS(&dsp_cfg);
sw_cfg.Dswitch = 0;
sw_cfg.Pswitch = 0;
sw_cfg.Nswitch = 0;
sw_cfg.Tswitch = 0;
AD5940_SWMatrixCfgS(&sw_cfg);
/* Enable all of them. They are automatically turned off during hibernate mode to save power */
AD5940_AFECtrlS(AFECTRL_HPREFPWR|AFECTRL_SINC2NOTCH, bTRUE);
AD5940_AFECtrlS(AFECTRL_SINC2NOTCH, bFALSE);
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
/* 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)
{
AppAMPCfg.InitSeqInfo.SeqId = SEQID_1;
AppAMPCfg.InitSeqInfo.SeqRamAddr = AppAMPCfg.SeqStartAddr;
AppAMPCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppAMPCfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppAMPCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppAMPSeqMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_SINC2;
clks_cal.DataCount = 1;
clks_cal.ADCSinc2Osr = AppAMPCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppAMPCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = AppAMPCfg.SysClkFreq/AppAMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
WaitClks += 15;
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin2);
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*250)); /* wait 250us */
AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE); /* Start ADC convert*/
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_ADCCNV|AFECTRL_SINC2NOTCH, bFALSE); /* Stop ADC */
AD5940_SEQGpioCtrlS(0);
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if(error == AD5940ERR_OK)
{
AppAMPCfg.MeasureSeqInfo.SeqId = SEQID_0;
AppAMPCfg.MeasureSeqInfo.SeqRamAddr = AppAMPCfg.InitSeqInfo.SeqRamAddr + AppAMPCfg.InitSeqInfo.SeqLen ;
AppAMPCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppAMPCfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppAMPCfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppAMPRtiaCal(void)
{
fImpPol_Type RtiaCalValue; /* Calibration result */
LPRTIACal_Type lprtia_cal;
AD5940_StructInit(&lprtia_cal, sizeof(lprtia_cal));
lprtia_cal.bPolarResult = bTRUE; /* Magnitude + Phase */
lprtia_cal.AdcClkFreq = AppAMPCfg.AdcClkFreq;
lprtia_cal.SysClkFreq = AppAMPCfg.SysClkFreq;
lprtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
lprtia_cal.ADCSinc2Osr = ADCSINC2OSR_22; /* Use SINC2 data as DFT data source */
lprtia_cal.DftCfg.DftNum = DFTNUM_2048; /* Maximum DFT number */
lprtia_cal.DftCfg.DftSrc = DFTSRC_SINC2NOTCH; /* For frequency under 12Hz, need to optimize DFT source. Use SINC3 data as DFT source */
lprtia_cal.DftCfg.HanWinEn = bTRUE;
lprtia_cal.fFreq = AppAMPCfg.AdcClkFreq/4/22/2048*3; /* Sample 3 period of signal, 13.317Hz here. Do not use DC method, because it needs ADC/PGA calibrated firstly(but it's faster) */
lprtia_cal.fRcal = AppAMPCfg.RcalVal;
lprtia_cal.LpTiaRtia = AppAMPCfg.LptiaRtiaSel;
lprtia_cal.LpAmpPwrMod = LPAMPPWR_NORM;
lprtia_cal.bWithCtia = bFALSE;
AD5940_LPRtiaCal(&lprtia_cal, &RtiaCalValue);
AppAMPCfg.RtiaCalValue = RtiaCalValue;
return AD5940ERR_OK;
}
/* This function provide application initialize. */
AD5940Err AppAMPInit(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 = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Do RTIA calibration */
if(((AppAMPCfg.ReDoRtiaCal == bTRUE) || \
AppAMPCfg.AMPInited == bFALSE) && AppAMPCfg.ExtRtia == bFALSE) /* Do calibration on the first initializaion */
{
AppAMPRtiaCal();
AppAMPCfg.ReDoRtiaCal = bFALSE;
}else
AppAMPCfg.RtiaCalValue.Magnitude = AppAMPCfg.ExtRtiaVal;
/* Reconfigure FIFO */
AD5940_FIFOCtrlS(DFTSRC_SINC3, 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 = AppAMPCfg.DataFifoSrc;
fifo_cfg.FIFOThresh = AppAMPCfg.FifoThresh;
AD5940_FIFOCfg(&fifo_cfg);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Start sequence generator */
/* Initialize sequencer generator */
if((AppAMPCfg.AMPInited == bFALSE)||\
(AppAMPCfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppAMPSeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppAMPSeqMeasureGen();
if(error != AD5940ERR_OK) return error;
AppAMPCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequencer */
AppAMPCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppAMPCfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppAMPCfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
/* Measurement sequence */
AppAMPCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppAMPCfg.MeasureSeqInfo);
// seq_cfg.SeqEnable = bTRUE;
// AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer, and wait for trigger. It's disabled in initialization sequence */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AppAMPCfg.PwrMod, AFEBW_250KHZ);
AppAMPCfg.AMPInited = bTRUE; /* AMP application has been initialized. */
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
static AD5940Err AppAMPRegModify(int32_t * const pData, uint32_t *pDataCount)
{
if(AppAMPCfg.NumOfData > 0)
{
AppAMPCfg.FifoDataCount += *pDataCount/4;
if(AppAMPCfg.FifoDataCount >= AppAMPCfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppAMPCfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return back to controller */
static AD5940Err AppAMPDataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t i, datacount;
datacount = *pDataCount;
float *pOut = (float *)pData;
for(i=0;i<datacount;i++)
{
pData[i] &= 0xffff;
pOut[i] = AppAMPCalcCurrent(pData[i]);
}
return AD5940ERR_OK;
}
/**
*/
AD5940Err AppAMPISR(void *pBuff, uint32_t *pCount)
{
uint32_t FifoCnt;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK);
*pCount = 0;
if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
{
FifoCnt = AD5940_FIFOGetCnt();
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppAMPRegModify(pBuff, &FifoCnt); /* If there is need to do AFE re-configure, do it here when AFE is in active state */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
//AD5940_EnterSleepS(); /* Manually put AFE back to hibernate mode. This operation only takes effect when register value is ACTIVE previously */
/* Process data */
AppAMPDataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}
/* Calculate voltage */
float AppAMPCalcVoltage(uint32_t ADCcode)
{
float kFactor = 1.835/1.82;
float fVolt = 0.0;
int32_t tmp = 0;
tmp = ADCcode - 32768;
switch(AppAMPCfg.ADCPgaGain)
{
case ADCPGA_1:
fVolt = ((float)(tmp)/32768)*(AppAMPCfg.ADCRefVolt/1)*kFactor;
break;
case ADCPGA_1P5:
fVolt = ((float)(tmp)/32768)*(AppAMPCfg.ADCRefVolt/1.5f)*kFactor;
break;
case ADCPGA_2:
fVolt = ((float)(tmp)/32768)*(AppAMPCfg.ADCRefVolt/2)*kFactor;
break;
case ADCPGA_4:
fVolt = ((float)(tmp)/32768)*(AppAMPCfg.ADCRefVolt/4)*kFactor;
break;
case ADCPGA_9:
fVolt = ((float)(tmp)/32768)*(AppAMPCfg.ADCRefVolt/9)*kFactor;
break;
}
return fVolt;
}
/* Calculate current in uA */
float AppAMPCalcCurrent(uint32_t ADCcode)
{
float fCurrent, fVoltage = 0.0;
fVoltage = AppAMPCalcVoltage(ADCcode);
fCurrent = fVoltage/AppAMPCfg.RtiaCalValue.Magnitude;
return -fCurrent*1000000;
}

View File

@ -0,0 +1,94 @@
/*!
*****************************************************************************
@file: Amperometric.h
@author: $Author: mlambe $
@brief: Amperometric measurement header file.
@version: $Revision: 766 $
@date: $Date: 2018-03-21 14:09:35 +0100 (Wed, 21 Mar 2018) $
-----------------------------------------------------------------------------
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 _AMPEROMETRIC_H_
#define _AMPEROMETRIC_H_
#include "AD5940.H"
#include "stdio.h"
#include "string.h"
#include "math.h"
#define DAC12BITVOLT_1LSB (2200.0f/4095) //mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) //mV
/*
Note: this example will use SEQID_0 as measurement sequence, and use SEQID_1 as init sequence.
SEQID_3 is used for calibration.
*/
typedef struct
{
/* Common configurations for all kinds of Application. */
BoolFlag bParaChanged; /* Indicate to generate sequence again. It's auto cleared by AppAMPInit */
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 */
BoolFlag ReDoRtiaCal; /* Set this flag to bTRUE when there is need to do calibration. */
float SysClkFreq; /* The real frequency of system clock */
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 AdcClkFreq; /* The real frequency of ADC clock */
uint32_t FifoThresh; /* FIFO threshold. Should be N*4 */
float AmpODR; /* in Hz. ODR decides the period of WakeupTimer who will trigger sequencer periodically.*/
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 RcalVal; /* Rcal value in Ohm */
float ADCRefVolt; /* Measured 1.82 V reference*/
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
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; /* SINC3 OSR selection. ADCSINC3OSR_2, ADCSINC3OSR_4 */
uint8_t ADCSinc2Osr; /* SINC2 OSR selection. ADCSINC2OSR_22...ADCSINC2OSR_1333 */
uint32_t DataFifoSrc; /* DataFIFO source. FIFOSRC_SINC3, FIFOSRC_DFT, FIFOSRC_SINC2NOTCH, FIFOSRC_VAR, FIFOSRC_MEAN*/
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 */
fImpPol_Type RtiaCalValue; /* Calibrated Rtia value */
float Vzero; /* Voltage on SE0 pin and Vzero, optimumly 1100mV*/
float SensorBias; /* Sensor bias voltage = VRE0 - VSE0 */
BoolFlag ExtRtia; /* Use internal or external Rtia */
float ExtRtiaVal; /* External Rtia value if using one */
BoolFlag AMPInited; /* 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 */
/* End */
}AppAMPCfg_Type;
/**
* int32_t type Impedance result in Cartesian coordinate
*/
typedef struct
{
float Current;
float Voltage;
}fAmpRes_Type;
#define AMPCTRL_START 0
#define AMPCTRL_STOPNOW 1
#define AMPCTRL_STOPSYNC 2
#define AMPCTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
AD5940Err AppAMPGetCfg(void *pCfg);
AD5940Err AppAMPInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppAMPISR(void *pBuff, uint32_t *pCount);
AD5940Err AppAMPCtrl(int32_t AmpCtrl, void *pPara);
float AppAMPCalcVoltage(uint32_t ADCcode);
float AppAMPCalcCurrent(uint32_t ADCcode);
#endif

662
examples/BATImpedance.c.txt Normal file
View File

@ -0,0 +1,662 @@
/*!
*****************************************************************************
@file: BATImpedance.c
@author: Neo Xu
@brief: Battery 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 "BATImpedance.h"
/*
Application configuration structure. Specified by user from template.
The variables are usable in this whole application.
It includes basic configuration for sequencer generator and application related parameters
*/
AppBATCfg_Type AppBATCfg =
{
.state = STATE_IDLE,
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.BatODR = 20.0, /* 20.0 Hz*/
.NumOfData = -1,
.PwrMod = AFEPWR_LP,
.ACVoltPP = 800.0,
.DCVolt = 1100.0f,
.SinFreq = 50000.0, /* 50kHz */
.RcalVal = 50.0, /* 50mOhm */
.ADCSinc3Osr = ADCSINC3OSR_4,
.ADCSinc2Osr = ADCSINC2OSR_22,
.DftNum = DFTNUM_16384,
.DftSrc = DFTSRC_SINC3,
.HanWinEn = bTRUE,
.FifoThresh = 4,
.BATInited = bFALSE,
.StopRequired = bFALSE,
.MeasSeqCycleCount = 0,
.SweepCfg.SweepEn = bTRUE,
.SweepCfg.SweepStart = 1000,
.SweepCfg.SweepStop = 100000.0,
.SweepCfg.SweepPoints = 101,
.SweepCfg.SweepLog = bFALSE,
.SweepCfg.SweepIndex = 0,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
AD5940Err AppBATGetCfg(void *pCfg)
{
if(pCfg){
*(AppBATCfg_Type**)pCfg = &AppBATCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
static void PreCharge(unsigned char channel)
{
void Arduino_WriteDn(uint32_t Dn, BoolFlag bHigh);
switch(channel)
{
case PRECHARGE_CH1: //00
Arduino_WriteDn(1<<3, bFALSE); //d3
Arduino_WriteDn(1<<4, bFALSE); //d4
break;
case PRECHARGE_CH2: //01
Arduino_WriteDn(1<<3, bTRUE);
Arduino_WriteDn(1<<4, bFALSE);
break;
case PRECHARGE_CH3://10
Arduino_WriteDn(1<<3, bFALSE);
Arduino_WriteDn(1<<4, bTRUE);
break;
default:
break;
}
AD5940_Delay10us(PRECHARGE_WAIT_MS*100);
Arduino_WriteDn(1<<3, bTRUE); //d3
Arduino_WriteDn(1<<4, bTRUE); //d4
}
AD5940Err AppBATCtrl(int32_t BatCtrl, void *pPara)
{
switch (BatCtrl)
{
case BATCTRL_START:
{
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if(AppBATCfg.BATInited == bFALSE)
return AD5940ERR_APPERROR;
AD5940_WriteReg(REG_AFE_SWMUX, 1<<0); /* control ADG636 to measure battery */
AD5940_WriteReg(REG_AFE_SYNCEXTDEVICE, 0x0);
PreCharge(PRECHARGE_BAT);
PreCharge(PRECHARGE_AMP);
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE);
AD5940_FIFOThrshSet(AppBATCfg.FifoThresh); /* DFT result contains both real and image. */
AD5940_FIFOCtrlS(FIFOSRC_DFT, bTRUE);
AppBATCfg.state = STATE_BATTERY;
/* Trigger sequence using MMR write */
AD5940_SEQMmrTrig(SEQID_0);
AppBATCfg.FifoDataCount = 0; /* restart */
break;
}
case BATCTRL_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 BATCTRL_STOPSYNC:
{
AppBATCfg.StopRequired = bTRUE;
break;
}
case BATCTRL_GETFREQ:
if(pPara)
{
if(AppBATCfg.SweepCfg.SweepEn == bTRUE)
*(float*)pPara = AppBATCfg.FreqofData;
else
*(float*)pPara = AppBATCfg.SinFreq;
}
break;
case BATCTRL_SHUTDOWN:
{
AppBATCtrl(BATCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
/* Turn off LPloop related blocks which are not controlled automatically by sleep 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;
case BATCTRL_MRCAL:
if(AD5940_WakeUp(10) > 10)
return AD5940ERR_WAKEUP;
//Settle input RC filter.
AD5940_WriteReg(REG_AFE_SWMUX, 0); //control ADG636 to measure rcal
AD5940_WriteReg(REG_AFE_SYNCEXTDEVICE, 0x4);
PreCharge(PRECHARGE_RCAL);
PreCharge(PRECHARGE_AMP);
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE);
AD5940_FIFOThrshSet(2);
AD5940_FIFOCtrlS(FIFOSRC_DFT, bTRUE); //enable FIFO
AD5940_AFECtrlS(AFECTRL_HPREFPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH, bTRUE);
AD5940_Delay10us(10000);
AppBATMeasureRCAL();
break;
default:
break;
}
return AD5940ERR_OK;
}
/* Generate init sequence */
static AD5940Err AppBATSeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type hs_loop;
LPLoopCfg_Type lp_loop;
DSPCfg_Type dsp_cfg;
float sin_freq;
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Disable all firstly. */
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, Use LP loop to provide DC Bias voltage. */
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
/* Determine buffer gain according to ACVoltPP */
hs_loop.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2;
hs_loop.HsDacCfg.HsDacGain = HSDACGAIN_1;
hs_loop.HsDacCfg.HsDacUpdateRate = 0x1B; //the maximum update rate is 16MHz/7
hs_loop.HsTiaCfg.DiodeClose = bFALSE;
hs_loop.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hs_loop.HsTiaCfg.HstiaCtia = 31; //HSTIA is not used.
hs_loop.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hs_loop.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_10K;
hs_loop.SWMatCfg.Dswitch = SWD_CE0;
hs_loop.SWMatCfg.Pswitch = SWP_AIN1;
hs_loop.SWMatCfg.Nswitch = SWN_AIN0; //AIN0 is connected to AIN4 externally by JP3.
hs_loop.SWMatCfg.Tswitch = 0; //T switch is not used.
hs_loop.WgCfg.WgType = WGTYPE_SIN;
hs_loop.WgCfg.GainCalEn = bFALSE;
hs_loop.WgCfg.OffsetCalEn = bFALSE;
if(AppBATCfg.SweepCfg.SweepEn == bTRUE)
{
AppBATCfg.FreqofData = AppBATCfg.SweepCfg.SweepStart;
AppBATCfg.SweepCurrFreq = AppBATCfg.SweepCfg.SweepStart;
AD5940_SweepNext(&AppBATCfg.SweepCfg, &AppBATCfg.SweepNextFreq);
sin_freq = AppBATCfg.SweepCurrFreq;
}
else
{
sin_freq = AppBATCfg.SinFreq;
AppBATCfg.FreqofData = sin_freq;
}
hs_loop.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(sin_freq, AppBATCfg.SysClkFreq);
hs_loop.WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)(AppBATCfg.ACVoltPP/800.0f*2047 + 0.5f);
hs_loop.WgCfg.SinCfg.SinOffsetWord = 0;
hs_loop.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&hs_loop);
//Use LP loop to output bias voltage on AIN4 pin, which provides voltage on AIN0(N switch).
lp_loop.LpDacCfg.LpdacSel = LPDAC0;
lp_loop.LpDacCfg.LpDacSrc = LPDACSRC_MMR;
lp_loop.LpDacCfg.LpDacSW = LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lp_loop.LpDacCfg.LpDacVzeroMux = LPDACVZERO_12BIT;
lp_loop.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_6BIT;
lp_loop.LpDacCfg.LpDacRef = LPDACREF_2P5;
lp_loop.LpDacCfg.DataRst = bFALSE;
lp_loop.LpDacCfg.PowerEn = bTRUE;
lp_loop.LpDacCfg.DacData12Bit = (uint32_t)((AppBATCfg.DCVolt-200)/2200.0f*4095);
lp_loop.LpDacCfg.DacData6Bit = 31; //not used. Set it to middle value.
lp_loop.LpAmpCfg.LpAmpSel = LPAMP0;
lp_loop.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM;
lp_loop.LpAmpCfg.LpPaPwrEn = bFALSE;
lp_loop.LpAmpCfg.LpTiaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaRf = LPTIARF_20K; //External cap is 1uF.
lp_loop.LpAmpCfg.LpTiaRload = LPTIARLOAD_SHORT;
lp_loop.LpAmpCfg.LpTiaRtia = LPTIARTIA_OPEN;
lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(7)|LPTIASW(5)|LPTIASW(9);
AD5940_LPLoopCfgS(&lp_loop);
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_AIN2;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_AIN3;
dsp_cfg.ADCBaseCfg.ADCPga = ADCPGA_1P5;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care because it's disabled */
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ;
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppBATCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppBATCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.DftCfg.DftNum = AppBATCfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppBATCfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppBATCfg.HanWinEn;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); /* Don't care about Statistic */
AD5940_DSPCfgS(&dsp_cfg);
/* Enable all of them. They are automatically turned off during hibernate mode to save power */
AD5940_AFECtrlS(AFECTRL_HPREFPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH, bTRUE);
/* Sequence end. */
AD5940_SEQGenInsert(SEQ_STOP()); /* Add one external 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)
{
AppBATCfg.InitSeqInfo.SeqId = SEQID_1;
AppBATCfg.InitSeqInfo.SeqRamAddr = AppBATCfg.SeqStartAddr;
AppBATCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppBATCfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppBATCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
//the sequence used to measure battery response voltage.
static AD5940Err AppBATSeqMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_DFT;
clks_cal.DftSrc = AppBATCfg.DftSrc;
clks_cal.DataCount = 1L<<(AppBATCfg.DftNum+2); /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppBATCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppBATCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = AppBATCfg.SysClkFreq/AppBATCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*250)); /* wait 250us for reference power up from hibernate mode. */
AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bTRUE); /* Enable Waveform generator, ADC power */
AD5940_SEQGenInsert(SEQ_WAIT(16*50000)); /* Wait for ADC ready. */
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_ADCCNV|AFECTRL_DFT/*|AFECTRL_WG*/|AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bFALSE); /* Stop ADC convert and DFT */
//AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
AppBATCfg.MeasSeqCycleCount = AD5940_SEQCycleTime();
AppBATCfg.MaxODR = 1/(((AppBATCfg.MeasSeqCycleCount + 10) / 16.0)* 1E-6) ;
if(AppBATCfg.BatODR > AppBATCfg.MaxODR)
{
/* We have requested a sampling rate that cannot be achieved with the time it
takes to acquire a sample.
*/
AppBATCfg.BatODR = AppBATCfg.MaxODR;
}
if(error == AD5940ERR_OK)
{
AppBATCfg.MeasureSeqInfo.SeqId = SEQID_0;
AppBATCfg.MeasureSeqInfo.SeqRamAddr = AppBATCfg.InitSeqInfo.SeqRamAddr + AppBATCfg.InitSeqInfo.SeqLen ;
AppBATCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppBATCfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppBATCfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
/* This function provide application initialize. */
AD5940Err AppBATInit(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 = bFALSE;
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 = AppBATCfg.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((AppBATCfg.BATInited == bFALSE)||\
(AppBATCfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppBATSeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppBATSeqMeasureGen();
if(error != AD5940ERR_OK) return error;
AppBATCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequencer */
AppBATCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppBATCfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppBATCfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
if(AppBATCfg.SweepCfg.SweepEn == bTRUE)
AppBATCheckFreq(AppBATCfg.SweepCfg.SweepStart);
else
AppBATCheckFreq(AppBATCfg.SinFreq);
/* Measurement sequence */
AppBATCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppBATCfg.MeasureSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AppBATCfg.PwrMod, AFEBW_250KHZ);
AD5940_WriteReg(REG_AFE_SWMUX, 1<<1);
AppBATCfg.BATInited = bTRUE; /* BAT application has been initialized. */
return AD5940ERR_OK;
}
/* Depending on frequency of Sin wave set optimum filter settings */
AD5940Err AppBATCheckFreq(float freq)
{
DSPCfg_Type dsp_cfg;
uint32_t WaitClks;
ClksCalInfo_Type clks_cal;
uint32_t SeqCmdBuff[2];
uint32_t SRAMAddr = 0;;
/* Step 1: Check Frequency */
if(freq < 0.51)
{
AppBATCfg.ADCSinc2Osr = ADCSINC2OSR_1067;
AppBATCfg.ADCSinc3Osr = ADCSINC3OSR_4;
AppBATCfg.DftSrc = DFTSRC_SINC2NOTCH;
}else if(freq < 5 )
{
AppBATCfg.ADCSinc2Osr = ADCSINC2OSR_640;
AppBATCfg.ADCSinc3Osr= ADCSINC3OSR_4;
AppBATCfg.DftSrc = DFTSRC_SINC2NOTCH;
}else if(freq <450)
{
AppBATCfg.ADCSinc2Osr = ADCSINC2OSR_178;
AppBATCfg.ADCSinc3Osr = ADCSINC3OSR_4;
AppBATCfg.DftSrc = DFTSRC_SINC2NOTCH;
}else if(freq < 80000)
{
AppBATCfg.ADCSinc3Osr = ADCSINC3OSR_4;
AppBATCfg.ADCSinc2Osr = ADCSINC2OSR_178;
AppBATCfg.DftSrc = DFTSRC_SINC3;
}
/* Step 2: Adjust ADCFILTERCON */
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_AIN2;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_AIN3;
dsp_cfg.ADCBaseCfg.ADCPga = ADCPGA_1P5;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care because it's disabled */
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ;
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppBATCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppBATCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.DftCfg.DftNum = AppBATCfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppBATCfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppBATCfg.HanWinEn;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); /* Don't care about Statistic */
AD5940_DSPCfgS(&dsp_cfg);
/* Step 3: Calculate clocks needed to get result to FIFO and update sequencer wait command */
clks_cal.DataType = DATATYPE_DFT;
clks_cal.DftSrc = AppBATCfg.DftSrc;
clks_cal.DataCount = 1L<<(AppBATCfg.DftNum+2); /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppBATCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppBATCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = AppBATCfg.SysClkFreq/AppBATCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/* Find start address of sequence in SRAM
Update WaitClks */
SRAMAddr = AppBATCfg.MeasureSeqInfo.SeqRamAddr;
SeqCmdBuff[0] = SEQ_WAIT(WaitClks);
AD5940_SEQCmdWrite(SRAMAddr+4, SeqCmdBuff, 1);
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
static AD5940Err AppBATRegModify(int32_t * const pData, uint32_t *pDataCount)
{
if(AppBATCfg.NumOfData > 0)
{
AppBATCfg.FifoDataCount += *pDataCount/4;
if(AppBATCfg.FifoDataCount >= AppBATCfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppBATCfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
if(AppBATCfg.SweepCfg.SweepEn) /* Need to set new frequency and set power mode */
{
AD5940_WGFreqCtrlS(AppBATCfg.SweepNextFreq, AppBATCfg.SysClkFreq);
AppBATCheckFreq(AppBATCfg.SweepNextFreq);
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return back to controller */
static AD5940Err AppBATDataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t DataCount = *pDataCount;
uint32_t DftResCount = DataCount/2;
fImpCar_Type * const pOut = (fImpCar_Type*)pData;
iImpCar_Type * pSrcData = (iImpCar_Type*)pData;
*pDataCount = 0;
DataCount = (DataCount/2)*2; /* We expect both Real and imaginary result. */
/* Convert DFT result to int32_t type */
for(uint32_t i=0; i<DataCount; i++)
{
pData[i] &= 0x3ffff;
if(pData[i]&(1<<17)) /* Bit17 is sign bit */
{
pData[i] |= 0xfffc0000; /* Data is 18bit in two's complement, bit17 is the sign bit */
}
}
if(AppBATCfg.state == STATE_RCAL)
{
/* Calculate the average voltage. */
AppBATCfg.RcalVolt.Image = 0;
AppBATCfg.RcalVolt.Real = 0;
for(uint32_t i=0;i<DftResCount;i++)
{
AppBATCfg.RcalVolt.Real += pSrcData[i].Real;
AppBATCfg.RcalVolt.Image += pSrcData[i].Image;
}
AppBATCfg.RcalVolt.Real /= DftResCount;
AppBATCfg.RcalVolt.Image /= DftResCount;
*pDataCount = 0; /* Report no result to upper application */
}
else if(AppBATCfg.state == STATE_BATTERY)
{
for(uint32_t i=0; i<DftResCount; i++)
{
fImpCar_Type BatImp, BatVolt;
BatVolt.Real = pSrcData->Real;
BatVolt.Image = pSrcData->Image;
pSrcData ++;
BatImp = AD5940_ComplexDivFloat(&BatVolt, &AppBATCfg.RcalVolt); //ratio measurement, Zbat = Vbat/Vrcal * Rcal;
BatImp.Image *= AppBATCfg.RcalVal;
BatImp.Real *= AppBATCfg.RcalVal;
pOut[i] = BatImp;
// printf("i: %d , %.2f , %.2f , %.2f , %.2f , %.2f , %.2f , %.2f\n",AppBATCfg.SweepCfg.SweepIndex, AppBATCfg.SweepCurrFreq, BatImp.Real, BatImp.Image, AppBATCfg.RcalVolt.Real, AppBATCfg.RcalVolt.Image, AppBATCfg.RcalVoltTable[AppBATCfg.SweepCfg.SweepIndex][0], AppBATCfg.RcalVoltTable[AppBATCfg.SweepCfg.SweepIndex][1]);
}
*pDataCount = DftResCount;
}
/* Calculate next frequency point */
if(AppBATCfg.SweepCfg.SweepEn == bTRUE)
{
AppBATCfg.FreqofData = AppBATCfg.SweepCurrFreq;
AppBATCfg.SweepCurrFreq = AppBATCfg.SweepNextFreq;
if(AppBATCfg.state == STATE_BATTERY)
{
AppBATCfg.RcalVolt.Real = AppBATCfg.RcalVoltTable[AppBATCfg.SweepCfg.SweepIndex][0];
AppBATCfg.RcalVolt.Image = AppBATCfg.RcalVoltTable[AppBATCfg.SweepCfg.SweepIndex][1];
}
AD5940_SweepNext(&AppBATCfg.SweepCfg, &AppBATCfg.SweepNextFreq);
}
return AD5940ERR_OK;
}
/**
*/
AD5940Err AppBATISR(void *pBuff, uint32_t *pCount)
{
uint32_t FifoCnt;
if(AppBATCfg.BATInited == bFALSE)
return AD5940ERR_APPERROR;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* Don't enter hibernate */
*pCount = 0;
if(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
{
/* Now there should be 2 data in FIFO */
FifoCnt = (AD5940_FIFOGetCnt()/2)*2;
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppBATRegModify(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. */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter hibernate mode */
/* Process data */
AppBATDataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}
AD5940Err AppBATMeasureRCAL(void)
{
uint32_t buff[100];
uint32_t temp;
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bFALSE); /* Disable INT0 interrupt for RCAL measurement. */
AppBATCfg.state = STATE_RCAL;
if(AppBATCfg.SweepCfg.SweepEn)
{
uint32_t i;
for(i=0;i<AppBATCfg.SweepCfg.SweepPoints;i++)
{
AD5940_SEQMmrTrig(SEQID_0);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DATAFIFOTHRESH) == bFALSE);
printf("i: %d Freq: %.2f ",AppBATCfg.SweepCfg.SweepIndex, AppBATCfg.SweepCurrFreq);
AppBATISR(buff, &temp);
AppBATCfg.RcalVoltTable[i][0] = AppBATCfg.RcalVolt.Real;
AppBATCfg.RcalVoltTable[i][1] = AppBATCfg.RcalVolt.Image;
printf(" RcalVolt:(%f,%f)\n", AppBATCfg.RcalVoltTable[i][0], AppBATCfg.RcalVoltTable[i][1]);
AD5940_Delay10us(10000);
}
AppBATCfg.RcalVolt.Real = AppBATCfg.RcalVoltTable[0][0];
AppBATCfg.RcalVolt.Image = AppBATCfg.RcalVoltTable[0][1];
}else
{
AD5940_SEQMmrTrig(SEQID_0);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DATAFIFOTHRESH) == bFALSE);
AppBATISR(buff, &temp);
}
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE);
return 0;
}
/**
* @}
*/

100
examples/BATImpedance.h.txt Normal file
View File

@ -0,0 +1,100 @@
/*!
@file: BATImpedance.h
@author: $Author: nxu2 $
@brief: Battery impedance measurement header file.
@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.
*****************************************************************************/
#ifndef _BAT_IMPEDANCE_H_
#define _BAT_IMPEDANCE_H_
#include "ad5940.h"
#include "stdio.h"
#include "string.h"
#include "math.h"
#define PRECHARGE_WAIT_MS 4000 //precharge time in ms.
#define PRECHARGE_CH1 1
#define PRECHARGE_CH2 2
#define PRECHARGE_CH3 3
#define PRECHARGE_RCAL PRECHARGE_CH1
#define PRECHARGE_BAT PRECHARGE_CH2
#define PRECHARGE_AMP PRECHARGE_CH3
/*
Note: this example will use SEQID_0 as measurement sequence, and use SEQID_1 as init sequence.
SEQID_3 is used for calibration.
*/
#define STATE_IDLE 0 /**< Initial state. */
#define STATE_RCAL 1 /**< Measure Rcal response voltage. */
#define STATE_BATTERY 2 /**< Measure battery response voltage. */
typedef struct
{
/* Common configurations for all kinds of Application. */
uint32_t state; /* 0: Init, 1: measure Rcal, 2: Measure Battery. */
BoolFlag bParaChanged; /* Indicate to generate sequence again. It's auto cleared by AppBATInit */
BoolFlag bDoCal; /* Need to do calibration. */
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 SysClkFreq; /* The real frequency of system clock */
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 AdcClkFreq; /* The real frequency of ADC clock */
uint32_t FifoThresh; /* FIFO threshold. Should be N*4 */
float BatODR; /* in Hz. ODR decides the period of WakeupTimer who will trigger sequencer periodically. DFT number and sample frequency decides the maxim ODR. */
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. */
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
float ACVoltPP; /* Final AC excitation voltage on pin AIN1 in mV peak to peak unit. */
float DCVolt; /* The DC bias voltage on AIN1 pin. Unit is mV. */
float RcalVal; /* Rcal value in mOhm */
float SinFreq; /* Frequency of excitation signal */
uint8_t ADCSinc3Osr; /* SINC3 OSR selection. ADCSINC3OSR_2, ADCSINC3OSR_4 */
uint8_t ADCSinc2Osr; /* SINC2 OSR selection. ADCSINC2OSR_22...ADCSINC2OSR_1333 */
uint32_t DftNum; /* DFT number */
uint32_t DftSrc; /* DFT Source */
BoolFlag HanWinEn; /* Enable Hanning window */
/* Sweep Function Control */
SoftSweepCfg_Type SweepCfg;
/* Private variables for internal usage */
float SweepCurrFreq;
float SweepNextFreq;
float FreqofData;
BoolFlag BATInited; /* 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 */
uint32_t MeasSeqCycleCount; /* How long the measurement sequence will take */
float MaxODR; /* Max ODR for sampling in this config */
fImpCar_Type RcalVolt; /* The measured Rcal resistor(R1) response voltage. */
float RcalVoltTable[100][2];
/* End */
}AppBATCfg_Type;
#define BATCTRL_START 0
#define BATCTRL_STOPNOW 1
#define BATCTRL_STOPSYNC 2
#define BATCTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
#define BATCTRL_MRCAL 5 /* Measure RCAL response voltage */
#define BATCTRL_GETFREQ 6
AD5940Err AppBATGetCfg(void *pCfg);
AD5940Err AppBATInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppBATISR(void *pBuff, uint32_t *pCount);
AD5940Err AppBATCtrl(int32_t BatCtrl, void *pPara);
AD5940Err AppBATCheckFreq(float freq);
AD5940Err AppBATMeasureRCAL(void);
#endif

710
examples/BIOZ-2Wire.c.txt Normal file
View File

@ -0,0 +1,710 @@
/******************************************************************************
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 "BIOZ-2Wire.h"
/**
* @note This example is modified from BIOZ example. This one is for 2-wire impedance measuremnt.
* The default pins used are CE0 and AIN2. The differnce with BIOZ is that the body voltage
* Measurment is replaced with excitation voltage measurment and it's only measured once.
*/
/*
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
*/
AppBIOZCfg_Type AppBIOZCfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.ReDoRtiaCal = bFALSE,
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.BIOZODR = 20.0, /* 20.0 Hz*/
.NumOfData = -1,
.RcalVal = 10000.0, /* 10kOhm */
.PwrMod = AFEPWR_LP,
.HstiaRtiaSel = HSTIARTIA_10K,
.CtiaSel = 16,
.ExcitBufGain = EXCITBUFGAIN_2,
.HsDacGain = HSDACGAIN_1,
.HsDacUpdateRate = 7,
.DacVoltPP = 600.0,
.SinFreq = 50000.0, /* 5000Hz */
.ADCPgaGain = ADCPGA_1P5,
.ADCSinc3Osr = ADCSINC3OSR_2,
.ADCSinc2Osr = ADCSINC2OSR_22,
.DftNum = DFTNUM_8192,
.DftSrc = DFTSRC_SINC3,
.HanWinEn = bTRUE,
.SweepCfg.SweepEn = bFALSE,
.SweepCfg.SweepStart = 10000,
.SweepCfg.SweepStop = 150000.0,
.SweepCfg.SweepPoints = 100,
.SweepCfg.SweepLog = bTRUE,
.SweepCfg.SweepIndex = 0,
.FifoThresh = 4, /* Must be 4 when SweepEn = bTRUE*/
.BIOZInited = bFALSE,
.StopRequired = bFALSE,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
AD5940Err AppBIOZGetCfg(void *pCfg)
{
if(pCfg){
*(AppBIOZCfg_Type**)pCfg = &AppBIOZCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
AD5940Err AppBIOZCtrl(int32_t BcmCtrl, void *pPara)
{
switch (BcmCtrl)
{
case BIOZCTRL_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(AppBIOZCfg.BIOZInited == bFALSE)
return AD5940ERR_APPERROR;
/* Start the wakeup timer */
wupt_cfg.WuptEn = bTRUE;
wupt_cfg.WuptEndSeq = WUPTENDSEQ_A;
wupt_cfg.WuptOrder[0] = SEQID_0;
wupt_cfg.SeqxSleepTime[SEQID_0] = (uint32_t)(AppBIOZCfg.WuptClkFreq/AppBIOZCfg.BIOZODR)-2-1;
wupt_cfg.SeqxWakeupTime[SEQID_0] = 1; /* The minimum value is 1. Do not set it to zero. Set it to 1 will spend 2 32kHz clock. */
AD5940_WUPTCfg(&wupt_cfg);
AppBIOZCfg.FifoDataCount = 0; /* restart */
#ifdef ADI_DEBUG
ADI_Print("BIOZ Start...\n");
#endif
break;
}
case BIOZCTRL_STOPNOW:
{
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Stop Wupt right now */
AD5940_WUPTCtrl(bFALSE);
AD5940_WUPTCtrl(bFALSE);
#ifdef ADI_DEBUG
ADI_Print("BIOZ Stop Now...\n");
#endif
break;
}
case BIOZCTRL_STOPSYNC:
{
#ifdef ADI_DEBUG
ADI_Print("BIOZ Stop SYNC...\n");
#endif
AppBIOZCfg.StopRequired = bTRUE;
break;
}
case BIOZCTRL_GETFREQ:
if(pPara)
{
if(AppBIOZCfg.SweepCfg.SweepEn == bTRUE)
*(float*)pPara = AppBIOZCfg.FreqofData;
else
*(float*)pPara = AppBIOZCfg.SinFreq;
}
break;
case BIOZCTRL_SHUTDOWN:
{
AppBIOZCtrl(BIOZCTRL_STOPNOW, 0); /* Stop the measurment if it's running. */
/* Turn off LPloop related blocks which are not controlled automatically by sleep 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 */
#ifdef ADI_DEBUG
ADI_Print("BIOZ Shut down...\n");
#endif
}
break;
default:
break;
}
return AD5940ERR_OK;
}
/* Generate init sequence */
static AD5940Err AppBIOZSeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type hs_loop;
DSPCfg_Type dsp_cfg;
float sin_freq;
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
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 powr*/
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
hs_loop.HsDacCfg.ExcitBufGain = AppBIOZCfg.ExcitBufGain;
hs_loop.HsDacCfg.HsDacGain = AppBIOZCfg.HsDacGain;
hs_loop.HsDacCfg.HsDacUpdateRate = AppBIOZCfg.HsDacUpdateRate;
hs_loop.HsTiaCfg.DiodeClose = bFALSE;
hs_loop.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hs_loop.HsTiaCfg.HstiaCtia = AppBIOZCfg.CtiaSel; /* 31pF + 2pF */
hs_loop.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hs_loop.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hs_loop.HsTiaCfg.HstiaRtiaSel = AppBIOZCfg.HstiaRtiaSel;
hs_loop.SWMatCfg.Dswitch = SWD_OPEN;
hs_loop.SWMatCfg.Pswitch = SWP_PL|SWP_PL2;
hs_loop.SWMatCfg.Nswitch = SWN_NL|SWN_NL2;
hs_loop.SWMatCfg.Tswitch = SWT_TRTIA;
hs_loop.WgCfg.WgType = WGTYPE_SIN;
hs_loop.WgCfg.GainCalEn = bFALSE;
hs_loop.WgCfg.OffsetCalEn = bFALSE;
if(AppBIOZCfg.SweepCfg.SweepEn == bTRUE)
{
AppBIOZCfg.SweepCfg.SweepIndex = 0;
AppBIOZCfg.FreqofData = AppBIOZCfg.SweepCfg.SweepStart;
AppBIOZCfg.SweepCurrFreq = AppBIOZCfg.SweepCfg.SweepStart;
AD5940_SweepNext(&AppBIOZCfg.SweepCfg, &AppBIOZCfg.SweepNextFreq);
sin_freq = AppBIOZCfg.SweepCurrFreq;
}
else
{
sin_freq = AppBIOZCfg.SinFreq;
AppBIOZCfg.FreqofData = sin_freq;
}
hs_loop.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(sin_freq, AppBIOZCfg.SysClkFreq);
hs_loop.WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)(AppBIOZCfg.DacVoltPP/800.0f*2047 + 0.5f);
hs_loop.WgCfg.SinCfg.SinOffsetWord = 0;
hs_loop.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&hs_loop);
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppBIOZCfg.ADCPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care becase it's disabled */
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* Tell filter block clock rate of ADC*/
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppBIOZCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppBIOZCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.DftCfg.DftNum = AppBIOZCfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppBIOZCfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppBIOZCfg.HanWinEn;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); /* Don't care about Statistic */
AD5940_DSPCfgS(&dsp_cfg);
/* Enable all of them. They are automatically turned off during hibernate mode to save power */
AD5940_AFECtrlS(AFECTRL_HPREFPWR|AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH, bTRUE);
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
/* Sequence end. */
AD5940_SEQGenInsert(SEQ_STOP()); /* Add one extral 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 seuqncer generator */
if(error == AD5940ERR_OK)
{
AppBIOZCfg.InitSeqInfo.SeqId = SEQID_1;
AppBIOZCfg.InitSeqInfo.SeqRamAddr = AppBIOZCfg.SeqStartAddr;
AppBIOZCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppBIOZCfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppBIOZCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppBIOZSeqMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
SWMatrixCfg_Type sw_cfg;
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_DFT;
clks_cal.DftSrc = AppBIOZCfg.DftSrc;
clks_cal.DataCount = 1L<<(AppBIOZCfg.DftNum+2); /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppBIOZCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppBIOZCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = AppBIOZCfg.SysClkFreq/AppBIOZCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin1/*|AGPIO_Pin5|AGPIO_Pin1*/);//GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
/* Configure switch matrix to connect the sensor */
sw_cfg.Dswitch = AppBIOZCfg.DswitchSel;
sw_cfg.Pswitch = AppBIOZCfg.PswitchSel;
sw_cfg.Nswitch = AppBIOZCfg.NswitchSel;
sw_cfg.Tswitch = AppBIOZCfg.TswitchSel|SWT_TRTIA;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_SEQGenInsert(SEQ_WAIT(16*250));
/* Step 1: Measure Current */
AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N);
AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
AD5940_SEQGenInsert(SEQ_WAIT(16*50));
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_SEQGenInsert(SEQ_WAIT(1));
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
/* Step 2: Measure Voltage */
AD5940_ADCMuxCfgS(ADCMUXP_VCE0, ADCMUXN_N_NODE);
AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
AD5940_SEQGenInsert(SEQ_WAIT(16*50));
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_SEQGenInsert(SEQ_WAIT(1));
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
sw_cfg.Dswitch = SWD_OPEN;
sw_cfg.Pswitch = SWP_PL|SWP_PL2;
sw_cfg.Nswitch = SWN_NL|SWN_NL2;
sw_cfg.Tswitch = SWT_TRTIA;
AD5940_SWMatrixCfgS(&sw_cfg); /* Float switches */
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop seuqncer generator */
if(error == AD5940ERR_OK)
{
AppBIOZCfg.MeasureSeqInfo.SeqId = SEQID_0;
AppBIOZCfg.MeasureSeqInfo.SeqRamAddr = AppBIOZCfg.InitSeqInfo.SeqRamAddr + AppBIOZCfg.InitSeqInfo.SeqLen ;
AppBIOZCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppBIOZCfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppBIOZCfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppBIOZRtiaCal(void)
{
HSRTIACal_Type hsrtia_cal;
FreqParams_Type freq_params;
if(AppBIOZCfg.SweepCfg.SweepEn == bTRUE)
{
hsrtia_cal.fFreq = AppBIOZCfg.SweepCfg.SweepStart;
freq_params = AD5940_GetFreqParameters(AppBIOZCfg.SweepCfg.SweepStart);
}
else
{
hsrtia_cal.fFreq = AppBIOZCfg.SinFreq;
freq_params = AD5940_GetFreqParameters(AppBIOZCfg.SinFreq);
}
if(freq_params.HighPwrMode == bTRUE)
hsrtia_cal.AdcClkFreq = 32e6;
else
hsrtia_cal.AdcClkFreq = 16e6;
hsrtia_cal.ADCSinc2Osr = freq_params.ADCSinc2Osr;
hsrtia_cal.ADCSinc3Osr = freq_params.ADCSinc3Osr;
hsrtia_cal.DftCfg.DftNum = freq_params.DftNum;
hsrtia_cal.DftCfg.DftSrc = freq_params.DftSrc;
hsrtia_cal.bPolarResult = bTRUE; /* We need magnitude and phase here */
hsrtia_cal.DftCfg.HanWinEn = AppBIOZCfg.HanWinEn;
hsrtia_cal.fRcal= AppBIOZCfg.RcalVal;
hsrtia_cal.HsTiaCfg.DiodeClose = bFALSE;
hsrtia_cal.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hsrtia_cal.HsTiaCfg.HstiaCtia = AppBIOZCfg.CtiaSel;
hsrtia_cal.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hsrtia_cal.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hsrtia_cal.HsTiaCfg.HstiaRtiaSel = AppBIOZCfg.HstiaRtiaSel;
hsrtia_cal.SysClkFreq = AppBIOZCfg.SysClkFreq;
if(AppBIOZCfg.SweepCfg.SweepEn == bTRUE)
{
uint32_t i;
AppBIOZCfg.SweepCfg.SweepIndex = 0; /* Reset index */
for(i=0;i<AppBIOZCfg.SweepCfg.SweepPoints;i++)
{
AD5940_HSRtiaCal(&hsrtia_cal, &AppBIOZCfg.RtiaCalTable[i]);
#ifdef ADI_DEBUG
ADI_Print("Freq:%.2f, (%f, %f)Ohm\n", hsrtia_cal.fFreq, AppBIOZCfg.RtiaCalTable[i].Real, AppBIOZCfg.RtiaCalTable[i].Image);
#endif
AD5940_SweepNext(&AppBIOZCfg.SweepCfg, &hsrtia_cal.fFreq);
freq_params = AD5940_GetFreqParameters(hsrtia_cal.fFreq);
if(freq_params.HighPwrMode == bTRUE)
{
hsrtia_cal.AdcClkFreq = 32e6;
/* Change clock to 32MHz oscillator */
AD5940_HPModeEn(bTRUE);
}
else
{
hsrtia_cal.AdcClkFreq = 16e6;
/* Change clock to 16MHz oscillator */
AD5940_HPModeEn(bFALSE);
}
hsrtia_cal.ADCSinc2Osr = freq_params.ADCSinc2Osr;
hsrtia_cal.ADCSinc3Osr = freq_params.ADCSinc3Osr;
hsrtia_cal.DftCfg.DftNum = freq_params.DftNum;
hsrtia_cal.DftCfg.DftSrc = freq_params.DftSrc;
}
AppBIOZCfg.SweepCfg.SweepIndex = 0; /* Reset index */
AppBIOZCfg.RtiaCurrValue = AppBIOZCfg.RtiaCalTable[0];
}
else
{
AD5940_HSRtiaCal(&hsrtia_cal, &AppBIOZCfg.RtiaCurrValue);
#ifdef ADI_DEBUG
ADI_Print("Freq:%.2f, (%f, %f)Ohm\n", hsrtia_cal.fFreq, AppBIOZCfg.RtiaCurrValue.Real, AppBIOZCfg.RtiaCurrValue.Image);
#endif
}
return AD5940ERR_OK;
}
/* This function provide application initialize. */
AD5940Err AppBIOZInit(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 = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Do RTIA calibration */
if((AppBIOZCfg.ReDoRtiaCal == bTRUE) || \
AppBIOZCfg.BIOZInited == bFALSE) /* Do calibration on the first initializaion */
{
AppBIOZRtiaCal();
AppBIOZCfg.ReDoRtiaCal = bFALSE;
}
/* 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 = AppBIOZCfg.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((AppBIOZCfg.BIOZInited == bFALSE)||\
(AppBIOZCfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppBIOZSeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppBIOZSeqMeasureGen();
if(error != AD5940ERR_OK) return error;
AppBIOZCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequencer */
AppBIOZCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppBIOZCfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppBIOZCfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Measurment sequence */
AppBIOZCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppBIOZCfg.MeasureSeqInfo);
AppBIOZCheckFreq(AppBIOZCfg.FreqofData);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AppBIOZCfg.BIOZInited = bTRUE; /* BIOZ application has been initialized. */
return AD5940ERR_OK;
}
/* Depending on frequency of Sin wave set optimum filter settings */
AD5940Err AppBIOZCheckFreq(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[2];
uint32_t SRAMAddr = 0;;
/* Step 1: Check Frequency */
freq_params = AD5940_GetFreqParameters(freq);
/* Set power mode */
if(freq_params.HighPwrMode == bTRUE)
{
/* Update HSDAC update rate */
hsdac_cfg.ExcitBufGain = AppBIOZCfg.ExcitBufGain;
hsdac_cfg.HsDacGain = AppBIOZCfg.HsDacGain;
hsdac_cfg.HsDacUpdateRate = 0x7;
AD5940_HSDacCfgS(&hsdac_cfg);
/*Update ADC rate */
filter_cfg.ADCRate = ADCRATE_1P6MHZ;
AppBIOZCfg.AdcClkFreq = 32e6;
/* Change clock to 32MHz oscillator */
AD5940_HPModeEn(bTRUE);
}else
{
/* Update HSDAC update rate */
hsdac_cfg.ExcitBufGain = AppBIOZCfg.ExcitBufGain;
hsdac_cfg.HsDacGain = AppBIOZCfg.HsDacGain;
hsdac_cfg.HsDacUpdateRate = 0x1B;
AD5940_HSDacCfgS(&hsdac_cfg);
/* Update ADC rate */
filter_cfg.ADCRate = ADCRATE_800KHZ;
AppBIOZCfg.AdcClkFreq = 16e6;
/* Change clock to 16MHz oscillator */
AD5940_HPModeEn(bFALSE);
}
/* 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 = AppBIOZCfg.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 = AppBIOZCfg.SysClkFreq/AppBIOZCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/* Maximum number of clocks is 0x3FFFFFFF. More are needed if the frequency is low */
if(WaitClks > 0x3FFFFFFF)
{
WaitClks /=2;
SRAMAddr = AppBIOZCfg.MeasureSeqInfo.SeqRamAddr;
SeqCmdBuff[0] = SEQ_WAIT(WaitClks);
AD5940_SEQCmdWrite(SRAMAddr+11, SeqCmdBuff, 1);
AD5940_SEQCmdWrite(SRAMAddr+12, SeqCmdBuff, 1);
AD5940_SEQCmdWrite(SRAMAddr+18, SeqCmdBuff, 1);
AD5940_SEQCmdWrite(SRAMAddr+19, SeqCmdBuff, 1);
}
else
{
SRAMAddr = AppBIOZCfg.MeasureSeqInfo.SeqRamAddr;
SeqCmdBuff[0] = SEQ_WAIT(WaitClks);
AD5940_SEQCmdWrite(SRAMAddr+11, SeqCmdBuff, 1);
AD5940_SEQCmdWrite(SRAMAddr+18, SeqCmdBuff, 1);
}
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
static AD5940Err AppBIOZRegModify(int32_t * const pData, uint32_t *pDataCount)
{
if(AppBIOZCfg.NumOfData > 0)
{
AppBIOZCfg.FifoDataCount += *pDataCount/4;
if(AppBIOZCfg.FifoDataCount >= AppBIOZCfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppBIOZCfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
if(AppBIOZCfg.SweepCfg.SweepEn) /* Need to set new frequency and set power mode */
{
AppBIOZCheckFreq(AppBIOZCfg.SweepNextFreq);
AD5940_WGFreqCtrlS(AppBIOZCfg.SweepNextFreq, AppBIOZCfg.SysClkFreq);
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return back to controller */
static AD5940Err AppBIOZDataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t DataCount = *pDataCount;
uint32_t ImpResCount = DataCount/4;
fImpCar_Type * pOut = (fImpCar_Type*)pData;
iImpCar_Type * pSrcData = (iImpCar_Type*)pData;
*pDataCount = 0;
DataCount = (DataCount/4)*4; /* One DFT result has two data in FIFO, real part and imaginary part. Each measurement has 2 DFT results, one for voltage measurement, one for current */
/* Convert DFT result to int32_t type */
for(uint32_t i=0; i<DataCount; i++)
{
pData[i] &= 0x3ffff;
if(pData[i]&(1<<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++)
{
fImpCar_Type DftCurr, DftVolt;
fImpCar_Type res;
DftCurr.Real = (float)pSrcData[i].Real;
DftCurr.Image = (float)pSrcData[i].Image;
DftVolt.Real = (float)pSrcData[i+1].Real;
DftVolt.Image = (float)pSrcData[i+1].Image;
DftCurr.Real = -DftCurr.Real;
DftCurr.Image = -DftCurr.Image;
DftVolt.Real = DftVolt.Real;
DftVolt.Image = DftVolt.Image;
res = AD5940_ComplexDivFloat(&DftCurr, &AppBIOZCfg.RtiaCurrValue); /* I=Vrtia/Zrtia */
res = AD5940_ComplexDivFloat(&DftVolt, &res);
pOut[i] = res;
}
*pDataCount = ImpResCount;
/* Calculate next frequency point */
if(AppBIOZCfg.SweepCfg.SweepEn == bTRUE)
{
AppBIOZCfg.FreqofData = AppBIOZCfg.SweepCurrFreq;
AppBIOZCfg.SweepCurrFreq = AppBIOZCfg.SweepNextFreq;
AppBIOZCfg.RtiaCurrValue = AppBIOZCfg.RtiaCalTable[AppBIOZCfg.SweepCfg.SweepIndex];
AD5940_SweepNext(&AppBIOZCfg.SweepCfg, &AppBIOZCfg.SweepNextFreq);
}
return AD5940ERR_OK;
}
/**
*/
AD5940Err AppBIOZISR(void *pBuff, uint32_t *pCount)
{
uint32_t BuffCount;
uint32_t FifoCnt;
if(AppBIOZCfg.BIOZInited == bFALSE)
return AD5940ERR_APPERROR;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* Don't enter hibernate */
*pCount = 0;
if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
{
/* Now there should be 4 data in FIFO */
FifoCnt = (AD5940_FIFOGetCnt()/4)*4;
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppBIOZRegModify(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 to save power. */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter hibernate mode */
/* Process data */
AppBIOZDataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}
/**
* @}
*/

97
examples/BIOZ-2Wire.h.txt Normal file
View File

@ -0,0 +1,97 @@
/*!
@file: BIOZ-2Wire.h
@author: Neo Xu
@brief: 4-wire BIOZ 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 _BODYCOMPOSITION_H_
#define _BODYCOMPOSITION_H_
#include "ad5940.h"
#include "stdio.h"
#include "string.h"
#include "math.h"
#define MAXSWEEP_POINTS 100 /* Need to know how much buffer is needed to save RTIA calibration result */
/*
Note: this example will use SEQID_0 as measurement sequence, and use SEQID_1 as init sequence.
SEQID_3 is used for calibration.
*/
typedef struct
{
/* Common configurations for all kinds of Application. */
BoolFlag bParaChanged; /* Indicate to generate sequence again. It's auto cleared by AppBIOZInit */
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 */
//BoolFlag bBioElecBoard; /* The code is same for BioElec board and AD5941Sens1 board. No changes are needed */
BoolFlag ReDoRtiaCal; /* Set this flag to bTRUE when there is need to do calibration. */
float SysClkFreq; /* The real frequency of system clock */
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 AdcClkFreq; /* The real frequency of ADC clock */
uint32_t FifoThresh; /* FIFO threshold. Should be N*2 */
float BIOZODR; /* in Hz. ODR decides the period of WakeupTimer who will trigger sequencer periodically. DFT number and sample frequency decides the maxim ODR. */
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 SinFreq; /* Frequency of excitation signal */
float RcalVal; /* Rcal value in Ohm */
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
float DacVoltPP; /* Final excitation voltage is DacVoltPP*DAC_PGA*EXCIT_GAIN, DAC_PGA= 1 or 0.2, EXCIT_GAIN=2 or 0.25. DAC output voltage in mV peak to peak. Maximum value is 800mVpp. Peak to peak voltage */
uint32_t ExcitBufGain; /* Select from EXCITBUFGAIN_2, EXCITBUFGAIN_0P25 */
uint32_t HsDacGain; /* Select from HSDACGAIN_1, HSDACGAIN_0P2 */
uint32_t HsDacUpdateRate; /* DAC update rate is SystemCLoock/Divider. The available value is 7 to 255. Set to 7 for better performance */
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; /* SINC3 OSR selection. ADCSINC3OSR_2, ADCSINC3OSR_4 */
uint8_t ADCSinc2Osr; /* SINC2 OSR selection. ADCSINC2OSR_22...ADCSINC2OSR_1333 */
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 CtiaSel; /* Select CTIA in pF unit from 0 to 31pF */
uint32_t DftNum; /* DFT number */
uint32_t DftSrc; /* DFT Source */
BoolFlag HanWinEn; /* Enable Hanning window */
/* Switch Configuration */
uint32_t DswitchSel;
uint32_t PswitchSel;
uint32_t NswitchSel;
uint32_t TswitchSel;
/* Sweep Function Control */
SoftSweepCfg_Type SweepCfg;
/* Private variables for internal usage */
float SweepCurrFreq;
float SweepNextFreq;
fImpCar_Type RtiaCurrValue; /* Calibrated Rtia value at current frequency */
fImpCar_Type RtiaCalTable[MAXSWEEP_POINTS]; /* Calibrated Rtia Value table */
float FreqofData; /* The frequency of latest data sampled */
BoolFlag BIOZInited; /* 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 */
/* End */
}AppBIOZCfg_Type;
#define BIOZCTRL_START 0
#define BIOZCTRL_STOPNOW 1
#define BIOZCTRL_STOPSYNC 2
#define BIOZCTRL_GETFREQ 3 /* Get Current frequency of returned data from ISR */
#define BIOZCTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
AD5940Err AppBIOZGetCfg(void *pCfg);
AD5940Err AppBIOZInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppBIOZISR(void *pBuff, uint32_t *pCount);
AD5940Err AppBIOZCtrl(int32_t BcmCtrl, void *pPara);
AD5940Err AppBIOZCheckFreq(float freq);
#endif

View File

@ -0,0 +1,680 @@
/*!
*****************************************************************************
@file: AD5940Main.c
@author: ADI
@brief: Used to control specific application and process data.
-----------------------------------------------------------------------------
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 "BodyImpedance-HiZ_Electrodes.h"
#include <complex.h>
DSPCfg_Type dsp_cfg; /*SKR: this variable needs to be global since the DFTlength is modified several times*/
/* This file contains auto generated source code that user defined */
/*
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
*/
AppBIACfg_Type AppBIACfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.ReDoRtiaCal = bFALSE,
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 32000000.0,
.BiaODR = 2.5, /* 20.0 Hz*/
.NumOfData = -1,
.RcalVal = 10000.0, /* 10kOhm */
.PwrMod = AFEPWR_HP,
.HstiaRtiaSel = HSTIARTIA_1K,
.CtiaSel = 16,
.ExcitBufGain = EXCITBUFGAIN_2,
.HsDacGain = HSDACGAIN_1,
.HsDacUpdateRate = 7,
.DacVoltPP = 800.0,
.SinFreq = 50000.0, /* 1000Hz */
.ADCPgaGain = ADCPGA_1P5,
.ADCSinc3Osr = ADCSINC3OSR_2,
.ADCSinc2Osr = ADCSINC2OSR_22,
.DftNum = DFTNUM_16384,
.DftSrc = DFTSRC_SINC3,
.HanWinEn = bTRUE,
.SweepCfg.SweepEn = bFALSE,
.SweepCfg.SweepStart = 10000,
.SweepCfg.SweepStop = 150000.0,
.SweepCfg.SweepPoints = 100,
.SweepCfg.SweepLog = bTRUE,
.SweepCfg.SweepIndex = 0,
.FifoThresh = 12,
.BIAInited = bFALSE,
.StopRequired = bFALSE,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
AD5940Err AppBIAGetCfg(void *pCfg)
{
if(pCfg){
*(AppBIACfg_Type**)pCfg = &AppBIACfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
AD5940Err AppBIACtrl(int32_t BcmCtrl, void *pPara)
{
switch (BcmCtrl)
{
case BIACTRL_START:
{
WUPTCfg_Type wupt_cfg;
if(AD5940_WakeUp(10) > 10) /* Wakup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if(AppBIACfg.BIAInited == 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] = (uint32_t)(AppBIACfg.WuptClkFreq/AppBIACfg.BiaODR)-2-1;
wupt_cfg.SeqxWakeupTime[SEQID_0] = 1; /* The minimum value is 1. Do not set it to zero. Set it to 1 will spend 2 32kHz clock. */
AD5940_WUPTCfg(&wupt_cfg);
AppBIACfg.FifoDataCount = 0; /* restart */
printf("BIA Start...\n");
break;
}
case BIACTRL_STOPNOW:
{
if(AD5940_WakeUp(10) > 10) /* Wakup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Start Wupt right now */
AD5940_WUPTCtrl(bFALSE);
AD5940_WUPTCtrl(bFALSE); /* @todo is it sure this will stop Wupt? */
printf("BIA Stop Now...\n");
break;
}
case BIACTRL_STOPSYNC:
{
printf("BIA Stop SYNC...\n");
AppBIACfg.StopRequired = bTRUE;
break;
}
case BIACTRL_GETFREQ:
if(pPara)
{
if(AppBIACfg.SweepCfg.SweepEn == bTRUE)
*(float*)pPara = AppBIACfg.FreqofData;
else
*(float*)pPara = AppBIACfg.SinFreq;
}
break;
case BIACTRL_SHUTDOWN:
{
AppBIACtrl(BIACTRL_STOPNOW, 0); /* Stop the measurment if it's running. */
/* Turn off LPloop related blocks which are not controlled automatically by sleep 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 */
printf("BIA Shut down...\n");
}
break;
default:
break;
}
return AD5940ERR_OK;
}
/* Generate init sequence */
static AD5940Err AppBIASeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type hs_loop;
LPLoopCfg_Type lp_loop;
float sin_freq;
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/);//GP6->control external mux, GP5 -> AD8233=OFF, GP1->RLD=OFF .
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 powr*/
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
hs_loop.HsDacCfg.ExcitBufGain = AppBIACfg.ExcitBufGain;
hs_loop.HsDacCfg.HsDacGain = AppBIACfg.HsDacGain;
hs_loop.HsDacCfg.HsDacUpdateRate = AppBIACfg.HsDacUpdateRate;
hs_loop.HsTiaCfg.DiodeClose = bFALSE;
hs_loop.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hs_loop.HsTiaCfg.HstiaCtia = AppBIACfg.CtiaSel; /* 16pF */
hs_loop.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hs_loop.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hs_loop.HsTiaCfg.HstiaRtiaSel = AppBIACfg.HstiaRtiaSel;
hs_loop.SWMatCfg.Dswitch = SWD_OPEN;
hs_loop.SWMatCfg.Pswitch = SWP_PL|SWP_PL2;
hs_loop.SWMatCfg.Nswitch = SWN_NL|SWN_NL2;
hs_loop.SWMatCfg.Tswitch = SWT_TRTIA;
hs_loop.WgCfg.WgType = WGTYPE_SIN;
hs_loop.WgCfg.GainCalEn = bFALSE;
hs_loop.WgCfg.OffsetCalEn = bFALSE;
if(AppBIACfg.SweepCfg.SweepEn == bTRUE)
{
AppBIACfg.FreqofData = AppBIACfg.SweepCfg.SweepStart;
AppBIACfg.SweepCurrFreq = AppBIACfg.SweepCfg.SweepStart;
AD5940_SweepNext(&AppBIACfg.SweepCfg, &AppBIACfg.SweepNextFreq);
sin_freq = AppBIACfg.SweepCurrFreq;
}
else
{
sin_freq = AppBIACfg.SinFreq;
AppBIACfg.FreqofData = sin_freq;
}
hs_loop.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(sin_freq, AppBIACfg.SysClkFreq);
hs_loop.WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)(AppBIACfg.DacVoltPP/800.0f*2047 + 0.5f);
hs_loop.WgCfg.SinCfg.SinOffsetWord = 0;
hs_loop.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&hs_loop);
lp_loop.LpDacCfg.LpdacSel = LPDAC0;
lp_loop.LpDacCfg.LpDacSrc = LPDACSRC_MMR;
lp_loop.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lp_loop.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT;
lp_loop.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT;
lp_loop.LpDacCfg.LpDacRef = LPDACREF_2P5;
lp_loop.LpDacCfg.DataRst = bFALSE;
lp_loop.LpDacCfg.PowerEn = bTRUE;
lp_loop.LpDacCfg.DacData12Bit = (uint32_t)((1100-200)/2200.0*4095);
lp_loop.LpDacCfg.DacData6Bit = 31;
lp_loop.LpAmpCfg.LpAmpSel = LPAMP0;
lp_loop.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM;
lp_loop.LpAmpCfg.LpPaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaRf = LPTIARF_20K;
lp_loop.LpAmpCfg.LpTiaRload = LPTIARLOAD_SHORT;
lp_loop.LpAmpCfg.LpTiaRtia = LPTIARTIA_OPEN;
lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(5)|LPTIASW(6)|LPTIASW(7)|LPTIASW(8)|LPTIASW(9)|LPTIASW(12)|LPTIASW(13); /* @todo Optimizanation needed for new silicon */
AD5940_LPLoopCfgS(&lp_loop);
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppBIACfg.ADCPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_2; /* Don't care becase it's disabled */
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_1P6MHZ; /* @todo Add explanation in UG that SINC3 filter clock is same as ADC, when ADC runs at 32MHz, clear this bit to enable clock divider for SINC3 filter. Make sure SINC3 clock is below 16MHz. */
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppBIACfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppBIACfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
/*dsp_cfg.ADCFilterCfg.Sinc2NotchClkEnable = bTRUE;
dsp_cfg.ADCFilterCfg.Sinc3ClkEnable = bTRUE;
dsp_cfg.ADCFilterCfg.WGClkEnable = bTRUE;
dsp_cfg.ADCFilterCfg.DFTClkEnable = bTRUE;*/
dsp_cfg.DftCfg.DftNum = AppBIACfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppBIACfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppBIACfg.HanWinEn;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); /* Don't care about Statistic */
AD5940_DSPCfgS(&dsp_cfg);
/* Enable all of them. They are automatically turned off during hibernate mode to save power */
AD5940_AFECtrlS(AFECTRL_HPREFPWR|AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH, bTRUE);
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
/* Sequence end. */
AD5940_SEQGenInsert(SEQ_STOP()); /* Add one extral 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 seuqncer generator */
if(error == AD5940ERR_OK)
{
AppBIACfg.InitSeqInfo.SeqId = SEQID_1;
AppBIACfg.InitSeqInfo.SeqRamAddr = AppBIACfg.SeqStartAddr;
AppBIACfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppBIACfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppBIACfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppBIASeqMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
SWMatrixCfg_Type sw_cfg;
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_DFT;
clks_cal.DftSrc = AppBIACfg.DftSrc;
clks_cal.DataCount = 1L<<(dsp_cfg.DftCfg.DftNum+2); /*wait can be no constant*//*clks_cal.DataCount = 1L<<(AppBIACfg.DftNum+2);*/ /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppBIACfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppBIACfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = AppBIACfg.SysClkFreq/AppBIACfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
/*0*/AD5940_SEQGpioCtrlS(AGPIO_Pin6/*|AGPIO_Pin5|AGPIO_Pin1*/);//GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
/*1*//*SKR: BUFSENCON is not done, it is done during the initialization (AD5940_REFCfgS)*/
/*2*//*SKR: AFECON is set to all connected during the initialization (AD5940_AFECtrlS)*/
/*3*/AD5940_SEQGenInsert(SEQ_WAIT(16*250)); /* @todo wait 250us?? */
/*4*/dsp_cfg.DftCfg.DftNum = DFTNUM_16384;
AD5940_DSPCfgS(&dsp_cfg);
clks_cal.DataCount = 1L<<(dsp_cfg.DftCfg.DftNum+2);
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/*5*/sw_cfg.Dswitch = SWD_RCAL0;
/*6*/sw_cfg.Pswitch = SWP_RCAL0;
/*7*/sw_cfg.Nswitch = SWN_RCAL1;
/*8*/sw_cfg.Tswitch = SWT_RCAL1|SWT_TRTIA;
/*9*/AD5940_SWMatrixCfgS(&sw_cfg);
/*10*/
/*11*/AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N);
/*12*/AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
/*13*/AD5940_SEQGenInsert(SEQ_WAIT(16*50));
/*14*/ AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
/*15*/AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
/*16*/AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
/*17*/dsp_cfg.DftCfg.DftNum = AppBIACfg.DftNum;
clks_cal.DataCount = 1L<<(dsp_cfg.DftCfg.DftNum+2);
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/*18*/ //I think it is not needed
/*19*/ //I think it is not needed
/*20*/sw_cfg.Dswitch = SWD_CE0;
/*21*/sw_cfg.Pswitch = SWP_CE0;
/*22*/sw_cfg.Nswitch = SWN_AIN1|SWN_AIN2|SWN_AIN3;
/*23*/sw_cfg.Tswitch = SWT_AIN1|SWT_AIN2|SWT_AIN3|SWT_TRTIA;
/*24*/AD5940_SWMatrixCfgS(&sw_cfg);
/*25*/ //I think it is not needed
/*26*/AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
/*27*/AD5940_SEQGenInsert(SEQ_WAIT(16*50));
/*28*/ AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
/*29*/AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
/*30*/AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
/*31*/ //I think it is not needed
/*32*/sw_cfg.Dswitch = SWD_CE0|SWD_AIN1|SWD_AIN3;
/*33*/sw_cfg.Pswitch = SWP_CE0|SWP_AIN1|SWP_AIN3;
/*34*/sw_cfg.Nswitch = SWN_AIN2;
/*35*/sw_cfg.Tswitch = SWT_AIN2|SWT_TRTIA;
/*36*/AD5940_SWMatrixCfgS(&sw_cfg);
/*37*/AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
/*38*/AD5940_SEQGenInsert(SEQ_WAIT(16*50));
/*39*/ AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
/*40*/AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
/*41*/AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
/*42*/sw_cfg.Dswitch = SWD_CE0|SWD_AIN1|SWD_AIN2;
/*43*/sw_cfg.Pswitch = SWP_CE0|SWP_AIN1|SWP_AIN2;
/*44*/sw_cfg.Nswitch = SWN_AIN3;
/*45*/sw_cfg.Tswitch = SWT_AIN3|SWT_TRTIA;
/*46*/AD5940_SWMatrixCfgS(&sw_cfg);
/*47*/AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
/*48*/AD5940_SEQGenInsert(SEQ_WAIT(16*50));
/*49*/ AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
/*50*/AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
/*51*/AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
/*52*/sw_cfg.Dswitch = SWD_CE0|SWD_AIN2|SWD_AIN3;
/*53*/sw_cfg.Pswitch = SWP_CE0|SWP_AIN2|SWP_AIN3;
/*54*/sw_cfg.Nswitch = SWN_AIN1;
/*55*/sw_cfg.Tswitch = SWT_AIN1|SWT_TRTIA;
/*56*/AD5940_SWMatrixCfgS(&sw_cfg);
/*57*/AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
/*58*/AD5940_SEQGenInsert(SEQ_WAIT(16*50));
/*59*/ AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
/*60*/AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
/*61*/AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
/*62*/sw_cfg.Dswitch = SWD_CE0|SWD_AIN2;
/*63*/sw_cfg.Pswitch = SWP_CE0|SWP_AIN2;
/*64*/sw_cfg.Nswitch = SWN_AIN1|SWN_AIN3;
/*65*/sw_cfg.Tswitch = SWT_AIN1|SWT_AIN3|SWT_TRTIA;
/*66*/AD5940_SWMatrixCfgS(&sw_cfg);
/*67*/AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
/*68*/AD5940_SEQGenInsert(SEQ_WAIT(16*50));
/*69*/ AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE); /* Start ADC convert and DFT */
/*70*/AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
/*71*/AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
/*72*/
/*73*/sw_cfg.Dswitch = SWD_OPEN;
/*74*/sw_cfg.Pswitch = SWP_PL|SWP_PL2;
/*75*/sw_cfg.Nswitch = SWN_NL|SWN_NL2;
/*76*/sw_cfg.Tswitch = SWT_OPEN;
/*77*/AD5940_SWMatrixCfgS(&sw_cfg); /* Float switches */
/*78*/
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->control MUX, GP5 -> AD8233=OFF, GP1->RLD=OFF .
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop seuqncer generator */
if(error == AD5940ERR_OK)
{
AppBIACfg.MeasureSeqInfo.SeqId = SEQID_0;
AppBIACfg.MeasureSeqInfo.SeqRamAddr = AppBIACfg.InitSeqInfo.SeqRamAddr + AppBIACfg.InitSeqInfo.SeqLen ;
AppBIACfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppBIACfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppBIACfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppBIARtiaCal(void)
{
HSRTIACal_Type hsrtia_cal;
hsrtia_cal.AdcClkFreq = AppBIACfg.AdcClkFreq;
hsrtia_cal.ADCSinc2Osr = AppBIACfg.ADCSinc2Osr;
hsrtia_cal.ADCSinc3Osr = AppBIACfg.ADCSinc3Osr;
hsrtia_cal.bPolarResult = bTRUE; /* We need magnitude and phase here */
hsrtia_cal.DftCfg.DftNum = AppBIACfg.DftNum;
hsrtia_cal.DftCfg.DftSrc = AppBIACfg.DftSrc;
hsrtia_cal.DftCfg.HanWinEn = AppBIACfg.HanWinEn;
hsrtia_cal.fRcal= AppBIACfg.RcalVal;
hsrtia_cal.HsTiaCfg.DiodeClose = bFALSE;
hsrtia_cal.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hsrtia_cal.HsTiaCfg.HstiaCtia = AppBIACfg.CtiaSel;
hsrtia_cal.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hsrtia_cal.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_TODE;
hsrtia_cal.HsTiaCfg.HstiaRtiaSel = AppBIACfg.HstiaRtiaSel;
hsrtia_cal.SysClkFreq = AppBIACfg.SysClkFreq;
if(AppBIACfg.SweepCfg.SweepEn == bTRUE)
{
uint32_t i;
AppBIACfg.SweepCfg.SweepIndex = 0; /* Reset index */
for(i=0;i<AppBIACfg.SweepCfg.SweepPoints;i++)
{
AD5940_SweepNext(&AppBIACfg.SweepCfg, &hsrtia_cal.fFreq);
AD5940_HSRtiaCal(&hsrtia_cal, AppBIACfg.RtiaCalTable[i]);
printf("Freq:%.2f,Mag:%.2f,Phase:%fDegree\n", hsrtia_cal.fFreq, AppBIACfg.RtiaCalTable[i][0], AppBIACfg.RtiaCalTable[i][1]*180/MATH_PI);
}
AppBIACfg.RtiaCurrValue[AppBIACfg.SweepCfg.SweepIndex] = AppBIACfg.RtiaCalTable[i][0];
AppBIACfg.RtiaCurrValue[AppBIACfg.SweepCfg.SweepIndex] = AppBIACfg.RtiaCalTable[i][0];
AppBIACfg.SweepCfg.SweepIndex = 0; /* Reset index */
}
else
{
hsrtia_cal.fFreq = AppBIACfg.SinFreq;
AD5940_HSRtiaCal(&hsrtia_cal, AppBIACfg.RtiaCurrValue);
printf("RtiaMag:%.2f,Phase:%fDegree\n", AppBIACfg.RtiaCurrValue[0], AppBIACfg.RtiaCurrValue[1]*180/MATH_PI);
}
return AD5940ERR_OK;
}
/* This function provide application initialize. */
AD5940Err AppBIAInit(uint32_t *pBuffer, uint32_t BufferSize)
{
AD5940Err error = AD5940ERR_OK;
SEQCfg_Type seq_cfg;
FIFOCfg_Type fifo_cfg;
if(AD5940_WakeUp(10) > 10) /* Wakup 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 = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Do RTIA calibration */
if((AppBIACfg.ReDoRtiaCal == bTRUE) || \
AppBIACfg.BIAInited == bFALSE) /* Do calibration on the first initializaion */
{
AppBIARtiaCal();
AppBIACfg.ReDoRtiaCal = bFALSE;
}
/* 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 = AppBIACfg.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((AppBIACfg.BIAInited == bFALSE)||\
(AppBIACfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppBIASeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppBIASeqMeasureGen();
if(error != AD5940ERR_OK) return error;
AppBIACfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequencer */
AppBIACfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppBIACfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppBIACfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
/* Measurment sequence */
AppBIACfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppBIACfg.MeasureSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AppBIACfg.PwrMod, AFEBW_250KHZ);
AD5940_WriteReg(REG_AFE_SWMUX, 1<<3);
AppBIACfg.BIAInited = bTRUE; /* BIA application has been initialized. */
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
static AD5940Err AppBIARegModify(int32_t * const pData, uint32_t *pDataCount)
{
if(AppBIACfg.NumOfData > 0)
{
AppBIACfg.FifoDataCount += *pDataCount/4;
if(AppBIACfg.FifoDataCount >= AppBIACfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppBIACfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
if(AppBIACfg.SweepCfg.SweepEn) /* Need to set new frequency and set power mode */
{
AD5940_WGFreqCtrlS(AppBIACfg.SweepNextFreq, AppBIACfg.SysClkFreq);
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return back to controller */
static AD5940Err AppBIADataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t DataCount = *pDataCount;
uint32_t ImpResCount = DataCount/12;
fImpCar_Type * const pOut = (fImpCar_Type*)pData;
iImpCar_Type * pSrcData = (iImpCar_Type*)pData;
*pDataCount = 0;
DataCount = (DataCount/12)*12;/* 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]&(1<<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 *zCalCurr, *I1, *I2, *I3, *I4, *I5;
zCalCurr = pSrcData++;
I1 = pSrcData++;
I2 = pSrcData++;
I3 = pSrcData++;
I4 = pSrcData++;
I5 = pSrcData++;
double complex vcal = -AppBIACfg.RcalVal * zCalCurr->Real + AppBIACfg.RcalVal * zCalCurr->Image*I; //real part is multiplied by -1 because current is 180<38> shifted. the imaginary part does not need be multiplied by -1 because DFT provides the -imaginary value, no the imaginary value
double complex a1 = vcal / (-I1->Real + I1->Image*I); //real part is multiplied by -1 because current is 180<38> shifted. the imaginary part does not need be multiplied by -1 because DFT provides the -imaginary value, no the imaginary value
double complex a2 = vcal / (-I2->Real + I2->Image*I); //real part is multiplied by -1 because current is 180<38> shifted. the imaginary part does not need be multiplied by -1 because DFT provides the -imaginary value, no the imaginary value
double complex a3 = vcal / (-I3->Real + I3->Image*I); //real part is multiplied by -1 because current is 180<38> shifted. the imaginary part does not need be multiplied by -1 because DFT provides the -imaginary value, no the imaginary value
double complex a4 = vcal / (-I4->Real + I4->Image*I); //real part is multiplied by -1 because current is 180<38> shifted. the imaginary part does not need be multiplied by -1 because DFT provides the -imaginary value, no the imaginary value
double complex a5 = vcal / (-I5->Real + I5->Image*I); //real part is multiplied by -1 because current is 180<38> shifted. the imaginary part does not need be multiplied by -1 because DFT provides the -imaginary value, no the imaginary value
double complex E1 = -2*a1*a2*a5*(a1*a2 + a1*a5 - a2*a5)/(a1*a1*a2*a2 - 2*a1*a1*a2*a5 + a1*a1*a5*a5 - 2*a1*a2*a2*a5 - 2*a1*a2*a5*a5 + a2*a2*a5*a5);
double complex E2 = -2*a1*a2*a5*(a1*a2 - a1*a5 + a2*a5)/(a1*a1*a2*a2 - 2*a1*a1*a2*a5 + a1*a1*a5*a5 - 2*a1*a2*a2*a5 - 2*a1*a2*a5*a5 + a2*a2*a5*a5);
double complex E3 = -2*a3*a4*a5*(a3*a4 + a3*a5 - a4*a5)/(a3*a3*a4*a4 - 2*a3*a3*a4*a5 + a3*a3*a5*a5 - 2*a3*a4*a4*a5 - 2*a3*a4*a5*a5 + a4*a4*a5*a5);
double complex E4 = -2*a3*a4*a5*(a3*a4 - a3*a5 + a4*a5)/(a3*a3*a4*a4 - 2*a3*a3*a4*a5 + a3*a3*a5*a5 - 2*a3*a4*a4*a5 - 2*a3*a4*a5*a5 + a4*a4*a5*a5);
double complex ZB = (-E1*E2*E3 - E1*E2*E4 - E1*E3*E4 + E1*E3*a5 + E1*E4*a5 - E2*E3*E4 + E2*E3*a5 + E2*E4*a5)/(E1*E3 + E1*E4 + E2*E3 + E2*E4);
pOut[(i*5)+0].Real = creal(E1);
pOut[(i*5)+0].Image = cimag(E1);
pOut[(i*5)+1].Real = creal(E2);
pOut[(i*5)+1].Image = cimag(E2);
pOut[(i*5)+2].Real = creal(E3);
pOut[(i*5)+2].Image = cimag(E3);
pOut[(i*5)+3].Real = creal(E4);
pOut[(i*5)+3].Image = cimag(E4);
pOut[(i*5)+4].Real = creal(ZB);
pOut[(i*5)+4].Image = cimag(ZB);
}
*pDataCount = ImpResCount;
/* Calculate next frequency point */
if(AppBIACfg.SweepCfg.SweepEn == bTRUE)
{
AppBIACfg.FreqofData = AppBIACfg.SweepCurrFreq;
AppBIACfg.SweepCurrFreq = AppBIACfg.SweepNextFreq;
AD5940_SweepNext(&AppBIACfg.SweepCfg, &AppBIACfg.SweepNextFreq);
AppBIACfg.RtiaCurrValue[0] = AppBIACfg.RtiaCalTable[AppBIACfg.SweepCfg.SweepIndex][0];
AppBIACfg.RtiaCurrValue[1] = AppBIACfg.RtiaCalTable[AppBIACfg.SweepCfg.SweepIndex][1];
}
return AD5940ERR_OK;
}
/**
*/
AD5940Err AppBIAISR(void *pBuff, uint32_t *pCount)
{
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
if(AppBIACfg.BIAInited == bFALSE)
return AD5940ERR_APPERROR;
if(AD5940_WakeUp(10) > 10) /* Wakup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
{
/* Now there should be 4 data in FIFO */
FifoCnt = (AD5940_FIFOGetCnt()/12)*12;
if(FifoCnt > BuffCount)
{
///@todo buffer is limited.
}
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppBIARegModify(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 */
/* Process data */
AppBIADataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}
/**
* @}
*/

View File

@ -0,0 +1,121 @@
/*!
@file: ImpSeqs.h
@author: $Author: nxu2 $
@brief: 4-wire BIA measurement header file.
@version: $Revision: 766 $
@date: $Date: 2017-08-21 14:09:35 +0100 (Mon, 21 Aug 2017) $
-----------------------------------------------------------------------------
Copyright (c) 2012-2017 Analog Devices, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Modified versions of the software must be conspicuously marked as such.
- This software is licensed solely and exclusively for use with processors
manufacTRUEd by or for Analog Devices, Inc.
- This software may not be combined or merged with other code in any manner
that would cause the software to become subject to terms and conditions
which differ from those listed here.
- Neither the name of Analog Devices, Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
- The use of this software may or may not infringe the patent rights of one
or more patent holders. This license does not release you from the
requirement that you obtain separate licenses from these patent holders
to use this software.
THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES, INC. AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-
INFRINGEMENT, TITLE, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ANALOG DEVICES, INC. OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, DAMAGES ARISING OUT OF
CLAIMS OF INTELLECTUAL PROPERTY RIGHTS INFRINGEMENT; PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#ifndef _BODYCOMPOSITIONJOSEMETHOD_H_
#define _BODYCOMPOSITIONJOSEMETHOD_H_
#include "ad5940.h"
#include "stdio.h"
#include "string.h"
#include "math.h"
#define MAXSWEEP_POINTS 100 /* Need to know how much buffer is needed to save RTIA calibration result */
/*
Note: this example will use SEQID_0 as measurment sequence, and use SEQID_1 as init sequence.
SEQID_3 is used for calibration.
*/
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; /* Measurment sequence start address in SRAM of AD5940 */
uint32_t MaxSeqLenCal;
/* Application related parameters */
//BoolFlag bBioElecBoard; /* The code is same for BioElec board and AD5941Sens1 board. No changes are needed */
BoolFlag ReDoRtiaCal; /* Set this flag to bTRUE when there is need to do calibration. */
float SysClkFreq; /* The real frequency of system clock */
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 AdcClkFreq; /* The real frequency of ADC clock */
uint32_t FifoThresh; /* FIFO threshold. Should be N*4 */
float BiaODR; /* in Hz. ODR decides the period of WakeupTimer who will trigger sequencer periodically. DFT number and sample frequency decides the maxim ODR. */
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 SinFreq; /* Frequency of excitation signal */
float RcalVal; /* Rcal value in Ohm */
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
float DacVoltPP; /* Final excitation voltage is DAC_VOLTpp*DAC_PGA*EXCIT_GAIN, DAC_PGA= 1 or 0.2, EXCIT_GAIN=2 or 0.25. DAC output voltage in mV peak to peak. Maximum value is 800mVpp. Peak to peak voltage */
uint32_t ExcitBufGain; /* Select from EXCITBUFGAIN_2, EXCITBUFGAIN_0P25 */
uint32_t HsDacGain; /* Select from HSDACGAIN_1, HSDACGAIN_0P2 */
uint32_t HsDacUpdateRate; /* DAC update rate is SystemCLoock/Divider. The available value is 7 to 255. Set to 7 for better perfomance */
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; /* SINC3 OSR selection. ADCSINC3OSR_2, ADCSINC3OSR_4 */
uint8_t ADCSinc2Osr; /* SINC2 OSR selection. ADCSINC2OSR_22...ADCSINC2OSR_1333 */
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 CtiaSel; /* Select CTIA in pF unit from 0 to 31pF */
uint32_t DftNum; /* DFT number */
uint32_t DftSrc; /* DFT Source */
BoolFlag HanWinEn; /* Enable Hanning window */
/* Sweep Function Control */
SoftSweepCfg_Type SweepCfg;
/* Private variables for internal usage */
float SweepCurrFreq;
float SweepNextFreq;
float RtiaCurrValue[2]; /* Calibrated Rtia value of current frequency */
float RtiaCalTable[MAXSWEEP_POINTS][2]; /* Calibrated Rtia Value table */
float FreqofData; /* The frequency of latest data sampled */
BoolFlag BIAInited; /* If the program run firstly, generated sequence commands */
SEQInfo_Type InitSeqInfo;
SEQInfo_Type MeasureSeqInfo;
BoolFlag StopRequired; /* After FIFO is ready, stop the measurment sequence */
uint32_t FifoDataCount; /* Count how many times impedance have been measured */
/* End */
}AppBIACfg_Type;
#define BIACTRL_START 0
#define BIACTRL_STOPNOW 1
#define BIACTRL_STOPSYNC 2
#define BIACTRL_GETFREQ 3 /* Get Current frequency of returned data from ISR */
#define BIACTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
AD5940Err AppBIAGetCfg(void *pCfg);
AD5940Err AppBIAInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppBIAISR(void *pBuff, uint32_t *pCount);
AD5940Err AppBIACtrl(int32_t BcmCtrl, void *pPara);
#endif

View File

@ -0,0 +1,636 @@
/*!
*****************************************************************************
@file: BodyImpedance.c
@author: Neo Xu
@brief: Body 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 "BodyImpedance.h"
/*
Application configuration structure. Specified by user from template.
The variables are usable in this whole application.
It includes basic configuration for sequencer generator and application related parameters
*/
AppBIACfg_Type AppBIACfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.ReDoRtiaCal = bFALSE,
.SysClkFreq = 16000000.0,
.LfoscClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.BiaODR = 20.0, /* 20.0 Hz*/
.NumOfData = -1,
.RcalVal = 10000.0, /* 10kOhm */
.PwrMod = AFEPWR_LP,
.HstiaRtiaSel = HSTIARTIA_1K,
.CtiaSel = 16,
.ExcitBufGain = EXCITBUFGAIN_2,
.HsDacGain = HSDACGAIN_1,
.HsDacUpdateRate = 7,
.DacVoltPP = 800.0,
.SinFreq = 50000.0, /* 50kHz */
.ADCPgaGain = ADCPGA_1,
.ADCSinc3Osr = ADCSINC3OSR_2,
.ADCSinc2Osr = ADCSINC2OSR_22,
.DftNum = DFTNUM_8192,
.DftSrc = DFTSRC_SINC3,
.HanWinEn = bTRUE,
.SweepCfg.SweepEn = bFALSE,
.SweepCfg.SweepStart = 10000,
.SweepCfg.SweepStop = 150000.0,
.SweepCfg.SweepPoints = 100,
.SweepCfg.SweepLog = bTRUE,
.SweepCfg.SweepIndex = 0,
.FifoThresh = 4,
.BIAInited = bFALSE,
.StopRequired = bFALSE,
.bRunning = bFALSE,
.MeasSeqCycleCount = 0,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
AD5940Err AppBIAGetCfg(void *pCfg)
{
if(pCfg){
*(AppBIACfg_Type**)pCfg = &AppBIACfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
AD5940Err AppBIACtrl(int32_t BcmCtrl, void *pPara)
{
switch (BcmCtrl)
{
case APPCTRL_START:
{
WUPTCfg_Type wupt_cfg;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if(AppBIACfg.BIAInited == 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] = (uint32_t)(AppBIACfg.LfoscClkFreq/AppBIACfg.BiaODR)-2-1;
wupt_cfg.SeqxWakeupTime[SEQID_0] = 1; /* The minimum value is 1. Do not set it to zero. Set it to 1 will spend 2 32kHz clock. */
AD5940_WUPTCfg(&wupt_cfg);
AppBIACfg.FifoDataCount = 0; /* restart */
AppBIACfg.bRunning = bTRUE;
#ifdef ADI_DEBUG
ADI_Print("BIA Start...\n");
#endif
break;
}
case APPCTRL_STOPNOW:
{
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Start Wupt right now */
AD5940_WUPTCtrl(bFALSE);
/* There is chance this operation will fail because sequencer could put AFE back
to hibernate mode just after waking up. Use STOPSYNC is better. */
AD5940_WUPTCtrl(bFALSE);
AppBIACfg.bRunning = bFALSE;
#ifdef ADI_DEBUG
ADI_Print("BIA Stop Now...\n");
#endif
break;
}
case APPCTRL_STOPSYNC:
{
#ifdef ADI_DEBUG
ADI_Print("BIA Stop SYNC...\n");
#endif
AppBIACfg.StopRequired = bTRUE;
break;
}
case BIACTRL_GETFREQ:
if(pPara)
{
if(AppBIACfg.SweepCfg.SweepEn == bTRUE)
*(float*)pPara = AppBIACfg.FreqofData;
else
*(float*)pPara = AppBIACfg.SinFreq;
}
break;
case APPCTRL_SHUTDOWN:
{
AppBIACtrl(APPCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
/* Turn off LPloop related blocks which are not controlled automatically by hibernate operation */
AFERefCfg_Type aferef_cfg;
LPLoopCfg_Type lp_loop;
memset(&aferef_cfg, 0, sizeof(aferef_cfg));
AD5940_REFCfgS(&aferef_cfg);
memset(&lp_loop, 0, sizeof(lp_loop));
AD5940_LPLoopCfgS(&lp_loop);
AD5940_EnterSleepS(); /* Enter Hibernate */
#ifdef ADI_DEBUG
ADI_Print("BIA Shut down...\n");
#endif
}
break;
case APPCTRL_RUNNING:
if(pPara == NULL)
return AD5940ERR_NULLP; /* Null pointer */
*(BoolFlag*)pPara = AppBIACfg.bRunning;
break;
default:
break;
}
return AD5940ERR_OK;
}
/* Generate init sequence */
static AD5940Err AppBIASeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type hs_loop;
LPLoopCfg_Type lp_loop;
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*/
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
hs_loop.HsDacCfg.ExcitBufGain = AppBIACfg.ExcitBufGain;
hs_loop.HsDacCfg.HsDacGain = AppBIACfg.HsDacGain;
hs_loop.HsDacCfg.HsDacUpdateRate = AppBIACfg.HsDacUpdateRate;
hs_loop.HsTiaCfg.DiodeClose = bFALSE;
hs_loop.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hs_loop.HsTiaCfg.HstiaCtia = AppBIACfg.CtiaSel; /* 31pF + 2pF */
hs_loop.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hs_loop.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hs_loop.HsTiaCfg.HstiaRtiaSel = AppBIACfg.HstiaRtiaSel;
hs_loop.SWMatCfg.Dswitch = SWD_OPEN;
hs_loop.SWMatCfg.Pswitch = SWP_PL|SWP_PL2;
hs_loop.SWMatCfg.Nswitch = SWN_NL|SWN_NL2;
hs_loop.SWMatCfg.Tswitch = SWT_TRTIA;
hs_loop.WgCfg.WgType = WGTYPE_SIN;
hs_loop.WgCfg.GainCalEn = bFALSE;
hs_loop.WgCfg.OffsetCalEn = bFALSE;
if(AppBIACfg.SweepCfg.SweepEn == bTRUE)
{
AppBIACfg.SweepCfg.SweepIndex = 0;
AppBIACfg.FreqofData = AppBIACfg.SweepCfg.SweepStart;
AppBIACfg.SweepCurrFreq = AppBIACfg.SweepCfg.SweepStart;
AD5940_SweepNext(&AppBIACfg.SweepCfg, &AppBIACfg.SweepNextFreq);
sin_freq = AppBIACfg.SweepCurrFreq;
}
else
{
sin_freq = AppBIACfg.SinFreq;
AppBIACfg.FreqofData = sin_freq;
}
hs_loop.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(sin_freq, AppBIACfg.SysClkFreq);
hs_loop.WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)(AppBIACfg.DacVoltPP/800.0f*2047 + 0.5f);
hs_loop.WgCfg.SinCfg.SinOffsetWord = 0;
hs_loop.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&hs_loop);
lp_loop.LpDacCfg.LpdacSel = LPDAC0;
lp_loop.LpDacCfg.LpDacSrc = LPDACSRC_MMR;
lp_loop.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA|LPDACSW_VBIAS2PIN|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lp_loop.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT;
lp_loop.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT;
lp_loop.LpDacCfg.LpDacRef = LPDACREF_2P5;
lp_loop.LpDacCfg.DataRst = bFALSE;
lp_loop.LpDacCfg.PowerEn = bTRUE;
lp_loop.LpDacCfg.DacData12Bit = (uint32_t)((1100-200)/2200.0*4095);
lp_loop.LpDacCfg.DacData6Bit = 31;
lp_loop.LpAmpCfg.LpAmpSel = LPAMP0;
lp_loop.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM;
lp_loop.LpAmpCfg.LpPaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaRf = LPTIARF_20K;
lp_loop.LpAmpCfg.LpTiaRload = LPTIARLOAD_SHORT;
lp_loop.LpAmpCfg.LpTiaRtia = LPTIARTIA_OPEN;
lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(5)|LPTIASW(6)|LPTIASW(7)|LPTIASW(8)|LPTIASW(9)|LPTIASW(12)|LPTIASW(13);
AD5940_LPLoopCfgS(&lp_loop);
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppBIACfg.ADCPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care because it's disabled */
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* Tell filter block clock rate of ADC*/
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppBIACfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppBIACfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.DftCfg.DftNum = AppBIACfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppBIACfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppBIACfg.HanWinEn;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); /* Don't care about Statistic */
AD5940_DSPCfgS(&dsp_cfg);
/* Enable all of them. They are automatically turned off during hibernate mode to save power */
AD5940_AFECtrlS(AFECTRL_HPREFPWR|AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
AFECTRL_SINC2NOTCH, bTRUE);
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
/* 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)
{
AppBIACfg.InitSeqInfo.SeqId = SEQID_1;
AppBIACfg.InitSeqInfo.SeqRamAddr = AppBIACfg.SeqStartAddr;
AppBIACfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppBIACfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppBIACfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppBIASeqMeasureGen(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 = AppBIACfg.DftSrc;
clks_cal.DataCount = 1L<<(AppBIACfg.DftNum+2); /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppBIACfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppBIACfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = AppBIACfg.SysClkFreq/AppBIACfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin6/*|AGPIO_Pin5|AGPIO_Pin1*/);//GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
AD5940_SEQGenInsert(SEQ_WAIT(16*250));
sw_cfg.Dswitch = SWD_CE0;
sw_cfg.Pswitch = SWP_CE0;
sw_cfg.Nswitch = SWN_AIN1;
sw_cfg.Tswitch = SWT_AIN1|SWT_TRTIA;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N);
AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
AD5940_SEQGenInsert(SEQ_WAIT(16*50));
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_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
AD5940_ADCMuxCfgS(ADCMUXP_AIN3, ADCMUXN_AIN2);
AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bTRUE); /* Enable Waveform generator, ADC power */
AD5940_SEQGenInsert(SEQ_WAIT(16*50)); //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_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE); /* Stop ADC convert and DFT */
sw_cfg.Dswitch = SWD_OPEN;
sw_cfg.Pswitch = SWP_PL|SWP_PL2;
sw_cfg.Nswitch = SWN_NL|SWN_NL2;
sw_cfg.Tswitch = SWT_TRTIA;
AD5940_SWMatrixCfgS(&sw_cfg); /* Float switches */
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
AppBIACfg.MeasSeqCycleCount = AD5940_SEQCycleTime();
AppBIACfg.MaxODR = 1/(((AppBIACfg.MeasSeqCycleCount + 10) / 16.0)* 1E-6) ;
if(AppBIACfg.BiaODR > AppBIACfg.MaxODR)
{
/* We have requested a sampling rate that cannot be achieved with the time it
takes to acquire a sample.
*/
AppBIACfg.BiaODR = AppBIACfg.MaxODR;
}
if(error == AD5940ERR_OK)
{
AppBIACfg.MeasureSeqInfo.SeqId = SEQID_0;
AppBIACfg.MeasureSeqInfo.SeqRamAddr = AppBIACfg.InitSeqInfo.SeqRamAddr + AppBIACfg.InitSeqInfo.SeqLen ;
AppBIACfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppBIACfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppBIACfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppBIARtiaCal(void)
{
HSRTIACal_Type hsrtia_cal;
hsrtia_cal.AdcClkFreq = AppBIACfg.AdcClkFreq;
hsrtia_cal.ADCSinc2Osr = AppBIACfg.ADCSinc2Osr;
hsrtia_cal.ADCSinc3Osr = AppBIACfg.ADCSinc3Osr;
hsrtia_cal.bPolarResult = bTRUE; /* We need magnitude and phase here */
hsrtia_cal.DftCfg.DftNum = AppBIACfg.DftNum;
hsrtia_cal.DftCfg.DftSrc = AppBIACfg.DftSrc;
hsrtia_cal.DftCfg.HanWinEn = AppBIACfg.HanWinEn;
hsrtia_cal.fRcal= AppBIACfg.RcalVal;
hsrtia_cal.HsTiaCfg.DiodeClose = bFALSE;
hsrtia_cal.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hsrtia_cal.HsTiaCfg.HstiaCtia = AppBIACfg.CtiaSel;
hsrtia_cal.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hsrtia_cal.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_TODE;
hsrtia_cal.HsTiaCfg.HstiaRtiaSel = AppBIACfg.HstiaRtiaSel;
hsrtia_cal.SysClkFreq = AppBIACfg.SysClkFreq;
hsrtia_cal.fFreq = AppBIACfg.SweepCfg.SweepStart;
if(AppBIACfg.SweepCfg.SweepEn == bTRUE)
{
uint32_t i;
AppBIACfg.SweepCfg.SweepIndex = 0; /* Reset index */
for(i=0;i<AppBIACfg.SweepCfg.SweepPoints;i++)
{
AD5940_HSRtiaCal(&hsrtia_cal, AppBIACfg.RtiaCalTable[i]);
#ifdef ADI_DEBUG
ADI_Print("Freq:%.2f, RTIA: Mag:%f Ohm, Phase:%.3f\n", hsrtia_cal.fFreq, AppBIACfg.RtiaCalTable[i][0], AppBIACfg.RtiaCalTable[i][1]);
#endif
AD5940_SweepNext(&AppBIACfg.SweepCfg, &hsrtia_cal.fFreq);
}
AppBIACfg.SweepCfg.SweepIndex = 0; /* Reset index */
AppBIACfg.RtiaCurrValue[0] = AppBIACfg.RtiaCalTable[AppBIACfg.SweepCfg.SweepIndex][0];
AppBIACfg.RtiaCurrValue[0] = AppBIACfg.RtiaCalTable[AppBIACfg.SweepCfg.SweepIndex][0];
}
else
{
hsrtia_cal.fFreq = AppBIACfg.SinFreq;
AD5940_HSRtiaCal(&hsrtia_cal, AppBIACfg.RtiaCurrValue);
#ifdef ADI_DEBUG
ADI_Print("RtiaMag:%.2f,Phase:%fDegree\n", AppBIACfg.RtiaCurrValue[0], AppBIACfg.RtiaCurrValue[1]*180/MATH_PI);
#endif
}
return AD5940ERR_OK;
}
/* This function provide application initialize. */
AD5940Err AppBIAInit(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 = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Do RTIA calibration */
if((AppBIACfg.ReDoRtiaCal == bTRUE) || \
AppBIACfg.BIAInited == bFALSE) /* Do calibration on the first initializaion */
{
AppBIARtiaCal();
AppBIACfg.ReDoRtiaCal = bFALSE;
}
/* 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 = AppBIACfg.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((AppBIACfg.BIAInited == bFALSE)||\
(AppBIACfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppBIASeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppBIASeqMeasureGen();
if(error != AD5940ERR_OK) return error;
AppBIACfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequencer */
AppBIACfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppBIACfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppBIACfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
/* Measurement sequence */
AppBIACfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppBIACfg.MeasureSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AppBIACfg.PwrMod, AFEBW_250KHZ);
AD5940_WriteReg(REG_AFE_SWMUX, 1<<3);
AppBIACfg.BIAInited = bTRUE; /* BIA application has been initialized. */
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
static AD5940Err AppBIARegModify(int32_t * const pData, uint32_t *pDataCount)
{
if(AppBIACfg.NumOfData > 0)
{
AppBIACfg.FifoDataCount += *pDataCount/4;
if(AppBIACfg.FifoDataCount >= AppBIACfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppBIACfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
AppBIACfg.StopRequired = bFALSE;
AppBIACfg.bRunning = bFALSE;
return AD5940ERR_OK;
}
if(AppBIACfg.SweepCfg.SweepEn) /* Need to set new frequency and set power mode */
{
AD5940_WGFreqCtrlS(AppBIACfg.SweepNextFreq, AppBIACfg.SysClkFreq);
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return back to controller */
static AD5940Err AppBIADataProcess(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;
if(pData[i]&(1<<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 *pDftVolt, *pDftCurr;
pDftCurr = pSrcData++;
pDftVolt = pSrcData++;
float VoltMag,VoltPhase;
float CurrMag, CurrPhase;
VoltMag = sqrt((float)pDftVolt->Real*pDftVolt->Real+(float)pDftVolt->Image*pDftVolt->Image);
VoltPhase = atan2(-pDftVolt->Image,pDftVolt->Real);
CurrMag = sqrt((float)pDftCurr->Real*pDftCurr->Real+(float)pDftCurr->Image*pDftCurr->Image);
CurrPhase = atan2(-pDftCurr->Image,pDftCurr->Real);
VoltMag = VoltMag/CurrMag*AppBIACfg.RtiaCurrValue[0];
VoltPhase = VoltPhase - CurrPhase + AppBIACfg.RtiaCurrValue[1];
//printf("V:%d,%d,I:%d,%d ",pDftVolt->Real,pDftVolt->Image, pDftCurr->Real, pDftCurr->Image);
pOut[i].Magnitude = VoltMag;
pOut[i].Phase = VoltPhase;
}
*pDataCount = ImpResCount;
/* Calculate next frequency point */
if(AppBIACfg.SweepCfg.SweepEn == bTRUE)
{
AppBIACfg.FreqofData = AppBIACfg.SweepCurrFreq;
AppBIACfg.SweepCurrFreq = AppBIACfg.SweepNextFreq;
AppBIACfg.RtiaCurrValue[0] = AppBIACfg.RtiaCalTable[AppBIACfg.SweepCfg.SweepIndex][0];
AppBIACfg.RtiaCurrValue[1] = AppBIACfg.RtiaCalTable[AppBIACfg.SweepCfg.SweepIndex][1];
AD5940_SweepNext(&AppBIACfg.SweepCfg, &AppBIACfg.SweepNextFreq);
}
return AD5940ERR_OK;
}
/**
*/
AD5940Err AppBIAISR(void *pBuff, uint32_t *pCount)
{
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
if(AppBIACfg.BIAInited == bFALSE)
return AD5940ERR_APPERROR;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* Don't enter hibernate */
*pCount = 0;
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);
AppBIARegModify(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. */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter hibernate mode */
/* Process data */
AppBIADataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}

View File

@ -0,0 +1,94 @@
/*!
@file: ImpSeqs.h
@author: $Author: nxu2 $
@brief: 4-wire BIA measurement header file.
@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.
*****************************************************************************/
#ifndef _BODYCOMPOSITION_H_
#define _BODYCOMPOSITION_H_
#include "ad5940.h"
#include "stdio.h"
#include "string.h"
#include "math.h"
#define MAXSWEEP_POINTS 100 /* Need to know how much buffer is needed to save RTIA calibration result */
/*
Note: this example will use SEQID_0 as measurement sequence, and use SEQID_1 as init sequence.
SEQID_3 is used for calibration.
*/
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 */
//BoolFlag bBioElecBoard; /* The code is same for BioElec board and AD5941Sens1 board. No changes are needed */
BoolFlag ReDoRtiaCal; /* Set this flag to bTRUE when there is need to do calibration. */
float SysClkFreq; /* The real frequency of system clock */
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 AdcClkFreq; /* The real frequency of ADC clock */
uint32_t FifoThresh; /* FIFO threshold. Should be N*4 */
float BiaODR; /* in Hz. ODR decides the period of WakeupTimer who will trigger sequencer periodically. DFT number and sample frequency decides the maxim ODR. */
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 SinFreq; /* Frequency of excitation signal */
float RcalVal; /* Rcal value in Ohm */
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
float DacVoltPP; /* Final excitation voltage is DAC_VOLTpp*DAC_PGA*EXCIT_GAIN, DAC_PGA= 1 or 0.2, EXCIT_GAIN=2 or 0.25. DAC output voltage in mV peak to peak. Maximum value is 800mVpp. Peak to peak voltage */
uint32_t ExcitBufGain; /* Select from EXCITBUFGAIN_2, EXCITBUFGAIN_0P25 */
uint32_t HsDacGain; /* Select from HSDACGAIN_1, HSDACGAIN_0P2 */
uint32_t HsDacUpdateRate; /* DAC update rate is SystemCLoock/Divider. The available value is 7 to 255. Set to 7 for better performance */
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; /* SINC3 OSR selection. ADCSINC3OSR_2, ADCSINC3OSR_4 */
uint8_t ADCSinc2Osr; /* SINC2 OSR selection. ADCSINC2OSR_22...ADCSINC2OSR_1333 */
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 CtiaSel; /* Select CTIA in pF unit from 0 to 31pF */
uint32_t DftNum; /* DFT number */
uint32_t DftSrc; /* DFT Source */
BoolFlag HanWinEn; /* Enable Hanning window */
/* Sweep Function Control */
SoftSweepCfg_Type SweepCfg;
/* Private variables for internal usage */
float SweepCurrFreq;
float SweepNextFreq;
float RtiaCurrValue[2]; /* Calibrated Rtia value of current frequency */
float RtiaCalTable[MAXSWEEP_POINTS][2]; /* Calibrated Rtia Value table */
float FreqofData; /* The frequency of latest data sampled */
BoolFlag BIAInited; /* 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 */
uint32_t MeasSeqCycleCount; /* How long the measurement sequence will take */
float MaxODR; /* Max ODR for sampling in this config */
/* End */
}AppBIACfg_Type;
#define BIACTRL_START 0
#define BIACTRL_STOPNOW 1
#define BIACTRL_STOPSYNC 2
#define BIACTRL_GETFREQ 3 /* Get Current frequency of returned data from ISR */
#define BIACTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
AD5940Err AppBIAGetCfg(void *pCfg);
AD5940Err AppBIAInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppBIAISR(void *pBuff, uint32_t *pCount);
AD5940Err AppBIACtrl(int32_t BcmCtrl, void *pPara);
#endif

View File

@ -0,0 +1,638 @@
/*!
*****************************************************************************
@file: ChronoAmperometric.c
@author: $Author: nxu2 $
@brief: Chrono-amperometric measurement sequences.
@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 "ChronoAmperometric.h"
/*
Application configuration structure. Specified by user from template.
The variables are usable in this whole application.
It includes basic configuration for sequencer generator and application related parameters
*/
AppCHRONOAMPCfg_Type AppCHRONOAMPCfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.FifoThresh = 1000,
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.AmpODR = 1,
.NumOfData = -1,
.RcalVal = 10000.0, /* 10kOhm */
.ExtRtiaVal = 0,
.PwrMod = AFEPWR_LP,
/* LPTIA Configure */
.LptiaRtiaSel = LPTIARTIA_10K,
.LpTiaRf = LPTIARF_1M,
.LpTiaRl = LPTIARLOAD_SHORT,
.ReDoRtiaCal = bTRUE,
.RtiaCalValue = 0,
/*LPDAC Configure */
.Vbias = 1100,
.Vzero = 1100,
/* Waveform Configuration */
.pulseAmplitude = 500, /* Amplitude of step in mV */
.pulseLength = 500, /* Length of transient in ms*/
.EndSeq = bFALSE, /* Flag to indicate sequence has finished */
/* ADC Configuration*/
.ADCPgaGain = ADCPGA_1P5,
.ADCSinc3Osr = ADCSINC3OSR_4,
.ADCSinc2Osr = ADCSINC2OSR_44,
.ADCRefVolt = 1.82, /* Measure voltage on ADCRefVolt pin and enter here*/
.DataFifoSrc = DATATYPE_SINC2, /* Data type must be SINC2 for chrono-amperometric measurement*/
.CHRONOAMPInited = bFALSE,
.StopRequired = bFALSE,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
AD5940Err AppCHRONOAMPGetCfg(void *pCfg)
{
if(pCfg){
*(AppCHRONOAMPCfg_Type**)pCfg = &AppCHRONOAMPCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
AD5940Err AppCHRONOAMPCtrl(int32_t AmpCtrl, void *pPara)
{
switch (AmpCtrl)
{
case CHRONOAMPCTRL_START:
{
WUPTCfg_Type wupt_cfg;
SEQCfg_Type seq_cfg;
AD5940_ReadReg(REG_AFE_ADCDAT); /* Any SPI Operation can wakeup AFE */
if(AppCHRONOAMPCfg.CHRONOAMPInited == bFALSE)
return AD5940ERR_APPERROR;
/* Configure FIFO and Sequencer for normal Amperometric Measurement */
AD5940_FIFOThrshSet(AppCHRONOAMPCfg.FifoThresh);
seq_cfg.SeqMemSize = SEQMEMSIZE_2KB; /* 2kB SRAM is used for sequencer, others for data FIFO */
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bTRUE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Configure Wakeup Timer*/
wupt_cfg.WuptEn = bTRUE;
wupt_cfg.WuptEndSeq = WUPTENDSEQ_A;
wupt_cfg.WuptOrder[0] = SEQID_0;
wupt_cfg.SeqxSleepTime[SEQID_0] = 1; /* The minimum value is 1. Do not set it to zero. Set it to 1 will spend 2 32kHz clock. */
wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(AppCHRONOAMPCfg.WuptClkFreq*AppCHRONOAMPCfg.AmpODR)-2-1;
AD5940_WUPTCfg(&wupt_cfg);
AppCHRONOAMPCfg.FifoDataCount = 0; /* restart */
break;
}
case CHRONOAMPCTRL_STOPNOW:
{
AD5940_ReadReg(REG_AFE_ADCDAT); /* Any SPI Operation can wakeup AFE */
/* 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 CHRONOAMPCTRL_STOPSYNC:
{
AppCHRONOAMPCfg.StopRequired = bTRUE;
break;
}
case CHRONOAMPCTRL_SHUTDOWN:
{
AppCHRONOAMPCtrl(CHRONOAMPCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
/* Turn off LPloop related blocks which are not controlled automatically by sleep 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 */
}
case CHRONOAMPCTRL_PULSETEST:
{
FIFOCfg_Type fifo_cfg;
AD5940_WUPTCtrl(bFALSE);
AppCHRONOAMPCfg.bMeasureTransient = bTRUE;
/* Reconfigure FIFO for Pulse test*/
AD5940_FIFOCtrlS(FIFOSRC_SINC3, 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_SINC2NOTCH;
fifo_cfg.FIFOThresh = 1000;
AD5940_FIFOCfg(&fifo_cfg);
/* Trigger sequence by MMR write */
AD5940_SEQMmrTrig(AppCHRONOAMPCfg.TransientSeqInfo.SeqId);
}
break;
default:
break;
}
return AD5940ERR_OK;
}
/* Generate init sequence */
static AD5940Err AppCHRONOAMPSeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
LPLoopCfg_Type lp_loop;
DSPCfg_Type dsp_cfg;
HSLoopCfg_Type hs_loop;
/* 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 = bTRUE;
aferef_cfg.Lp1V8BuffEn = bTRUE;
/* LP reference control - turn off them to save power*/
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
lp_loop.LpDacCfg.LpdacSel = LPDAC0;
lp_loop.LpDacCfg.LpDacSrc = LPDACSRC_MMR;
lp_loop.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA|/*LPDACSW_VBIAS2PIN|*/LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lp_loop.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT;
lp_loop.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT;
lp_loop.LpDacCfg.LpDacRef = LPDACREF_2P5;
lp_loop.LpDacCfg.DataRst = bFALSE;
lp_loop.LpDacCfg.PowerEn = bTRUE;
lp_loop.LpDacCfg.DacData6Bit = (uint32_t)((AppCHRONOAMPCfg.Vzero-200)/DAC6BITVOLT_1LSB);
lp_loop.LpDacCfg.DacData12Bit =(int32_t)((AppCHRONOAMPCfg.SensorBias)/DAC12BITVOLT_1LSB) + lp_loop.LpDacCfg.DacData6Bit*64;
if(lp_loop.LpDacCfg.DacData12Bit>lp_loop.LpDacCfg.DacData6Bit*64)
lp_loop.LpDacCfg.DacData12Bit--;
lp_loop.LpAmpCfg.LpAmpSel = LPAMP0;
lp_loop.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM;
lp_loop.LpAmpCfg.LpPaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaPwrEn = bTRUE;
lp_loop.LpAmpCfg.LpTiaRf = AppCHRONOAMPCfg.LpTiaRf;
lp_loop.LpAmpCfg.LpTiaRload = AppCHRONOAMPCfg.LpTiaRl;
if(AppCHRONOAMPCfg.ExtRtia == bTRUE)
{
lp_loop.LpAmpCfg.LpTiaRtia = LPTIARTIA_OPEN;
lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(9)|LPTIASW(2)|LPTIASW(4)|LPTIASW(5)/*|LPTIASW(12)*/|LPTIASW(13);
}else
{
lp_loop.LpAmpCfg.LpTiaRtia = AppCHRONOAMPCfg.LptiaRtiaSel;
lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(5)|LPTIASW(2)|LPTIASW(4)/*|LPTIASW(12)*/|LPTIASW(13);
}
AD5940_LPLoopCfgS(&lp_loop);
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_LPTIA0_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_LPTIA0_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppCHRONOAMPCfg.ADCPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; /* Don't care because it's disabled */
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* Tell filter block clock rate of ADC*/
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppCHRONOAMPCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppCHRONOAMPCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg)); /* Don't care about Statistic */
AD5940_DSPCfgS(&dsp_cfg);
hs_loop.SWMatCfg.Dswitch = 0;
hs_loop.SWMatCfg.Pswitch = 0;
hs_loop.SWMatCfg.Nswitch = 0;
hs_loop.SWMatCfg.Tswitch = 0;
AD5940_HSLoopCfgS(&hs_loop);
/* Enable all of them. They are automatically turned off during hibernate mode to save power */
AD5940_AFECtrlS(AFECTRL_HPREFPWR|AFECTRL_SINC2NOTCH, bTRUE);
AD5940_SEQGpioCtrlS(0);
/* 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)
{
AppCHRONOAMPCfg.InitSeqInfo.SeqId = SEQID_1;
AppCHRONOAMPCfg.InitSeqInfo.SeqRamAddr = AppCHRONOAMPCfg.SeqStartAddr;
AppCHRONOAMPCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppCHRONOAMPCfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppCHRONOAMPCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppCHRONOAMPTransientMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
uint32_t VbiasCode, VzeroCode;
uint32_t WaitClks;
ClksCalInfo_Type clks_cal;
if(AppCHRONOAMPCfg.DataFifoSrc != DATATYPE_SINC2)
return AD5940ERR_ERROR; /* FIFO data must be SINC2 filter for measuring transient */
/* Calculate LPDAC codes */
VzeroCode = (uint32_t)((AppCHRONOAMPCfg.Vzero-200)/DAC6BITVOLT_1LSB);
VbiasCode = (int32_t)((AppCHRONOAMPCfg.pulseAmplitude + AppCHRONOAMPCfg.SensorBias)/DAC12BITVOLT_1LSB) + VzeroCode*64;
if(VbiasCode < (VzeroCode*64))
VbiasCode --;
/* Truncate */
if(VbiasCode > 4095) VbiasCode = 4095;
if(VzeroCode > 63) VzeroCode = 63;
clks_cal.DataType = AppCHRONOAMPCfg.DataFifoSrc;
clks_cal.DataCount = AppCHRONOAMPCalcDataNum(AppCHRONOAMPCfg.pulseLength);
clks_cal.ADCSinc2Osr = AppCHRONOAMPCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppCHRONOAMPCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = AppCHRONOAMPCfg.SysClkFreq/AppCHRONOAMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin1);
AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*250));
AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE); /* Start ADC conversion before applying step to capture peak */
AD5940_WriteReg(REG_AFE_LPDACDAT0, VzeroCode<<12|VbiasCode);
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_ADCCNV, bFALSE); /* Stop ADC */
AD5940_WriteReg(REG_AFE_LPDACDAT0,(uint32_t)((AppCHRONOAMPCfg.Vzero-200)/DAC6BITVOLT_1LSB)<<12|(int32_t)((AppCHRONOAMPCfg.SensorBias)/DAC12BITVOLT_1LSB) + VzeroCode*64);
AD5940_SEQGpioCtrlS(0);
/* 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. */
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if(error == AD5940ERR_OK)
{
AppCHRONOAMPCfg.TransientSeqInfo.SeqId = SEQID_2;
AppCHRONOAMPCfg.TransientSeqInfo.SeqRamAddr = AppCHRONOAMPCfg.MeasureSeqInfo.SeqRamAddr + AppCHRONOAMPCfg.MeasureSeqInfo.SeqLen ;
AppCHRONOAMPCfg.TransientSeqInfo.pSeqCmd = pSeqCmd;
AppCHRONOAMPCfg.TransientSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppCHRONOAMPCfg.TransientSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppCHRONOAMPSeqMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
uint32_t const *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
ClksCalInfo_Type clks_cal;
clks_cal.DataType = AppCHRONOAMPCfg.DataFifoSrc;
clks_cal.DataCount = 1;
clks_cal.ADCSinc2Osr = AppCHRONOAMPCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppCHRONOAMPCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = 0;
clks_cal.RatioSys2AdcClk = AppCHRONOAMPCfg.SysClkFreq/AppCHRONOAMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin1);
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_SINC2NOTCH, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*250));
AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_ADCCNV, bFALSE); /* Stop ADC */
AD5940_SEQGpioCtrlS(0);
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if(error == AD5940ERR_OK)
{
AppCHRONOAMPCfg.MeasureSeqInfo.SeqId = SEQID_0;
AppCHRONOAMPCfg.MeasureSeqInfo.SeqRamAddr = AppCHRONOAMPCfg.InitSeqInfo.SeqRamAddr + AppCHRONOAMPCfg.InitSeqInfo.SeqLen ;
AppCHRONOAMPCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppCHRONOAMPCfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppCHRONOAMPCfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppCHRONOAMPRtiaCal(void)
{
fImpPol_Type RtiaCalValue; /* Calibration result */
LPRTIACal_Type lprtia_cal;
AD5940_StructInit(&lprtia_cal, sizeof(lprtia_cal));
lprtia_cal.LpAmpSel = LPAMP0;
lprtia_cal.bPolarResult = bTRUE; /* Magnitude + Phase */
lprtia_cal.AdcClkFreq = AppCHRONOAMPCfg.AdcClkFreq;
lprtia_cal.SysClkFreq = AppCHRONOAMPCfg.SysClkFreq;
lprtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
lprtia_cal.ADCSinc2Osr = ADCSINC2OSR_22; /* Use SINC2 data as DFT data source */
lprtia_cal.DftCfg.DftNum = DFTNUM_2048; /* Maximum DFT number */
lprtia_cal.DftCfg.DftSrc = DFTSRC_SINC2NOTCH;
lprtia_cal.DftCfg.HanWinEn = bTRUE;
lprtia_cal.fFreq = AppCHRONOAMPCfg.AdcClkFreq/4/22/2048*3; /* Sample 3 period of signal, 13.317Hz here. Do not use DC method, because it needs ADC/PGA calibrated firstly(but it's faster) */
lprtia_cal.fRcal = AppCHRONOAMPCfg.RcalVal;
lprtia_cal.LpAmpPwrMod = LPAMPPWR_NORM;
lprtia_cal.bWithCtia = bFALSE;
lprtia_cal.LpTiaRtia = AppCHRONOAMPCfg.LptiaRtiaSel;
AD5940_LPRtiaCal(&lprtia_cal, &RtiaCalValue);
AppCHRONOAMPCfg.RtiaCalValue = RtiaCalValue;
return AD5940ERR_OK;
}
/**
* @brief Initialize the amperometric test. Call this function every time before starting amperometric test.
* @param pBuffer: the buffer for sequencer generator. Only need to provide it for the first time.
* @param BufferSize: The buffer size start from pBuffer.
* @return return error code.
*/
AD5940Err AppCHRONOAMPInit(uint32_t *pBuffer, uint32_t BufferSize)
{
AD5940Err error = AD5940ERR_OK;
SEQCfg_Type seq_cfg;
FIFOCfg_Type fifo_cfg;
AD5940_ReadReg(REG_AFE_ADCDAT); /* Any SPI Operation can wakeup AFE */
/* 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 = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Do RTIA calibration */
if(((AppCHRONOAMPCfg.ReDoRtiaCal == bTRUE) || \
AppCHRONOAMPCfg.CHRONOAMPInited == bFALSE) && AppCHRONOAMPCfg.ExtRtia == bFALSE)
{
AppCHRONOAMPRtiaCal();
AppCHRONOAMPCfg.ReDoRtiaCal = bFALSE;
}
/* Reconfigure FIFO */
AD5940_FIFOCtrlS(FIFOSRC_SINC3, 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_SINC2NOTCH;
fifo_cfg.FIFOThresh = AppCHRONOAMPCfg.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((AppCHRONOAMPCfg.CHRONOAMPInited == bFALSE)||\
(AppCHRONOAMPCfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppCHRONOAMPSeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppCHRONOAMPSeqMeasureGen();
if(error != AD5940ERR_OK) return error;
/* Generate transient sequence */
error = AppCHRONOAMPTransientMeasureGen();
if(error != AD5940ERR_OK) return error;
AppCHRONOAMPCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequencer */
AppCHRONOAMPCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppCHRONOAMPCfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppCHRONOAMPCfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ);
/* Transient sequence */
AppCHRONOAMPCfg.TransientSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppCHRONOAMPCfg.TransientSeqInfo);
/* Measurement sequence */
AppCHRONOAMPCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppCHRONOAMPCfg.MeasureSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AppCHRONOAMPCfg.PwrMod, AFEBW_250KHZ);
AppCHRONOAMPCfg.CHRONOAMPInited = bTRUE; /* CHRONOAMP application has been initialized. */
AppCHRONOAMPCfg.bMeasureTransient = bFALSE;
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
static AD5940Err AppCHRONOAMPRegModify(int32_t * const pData, uint32_t *pDataCount)
{
FIFOCfg_Type fifo_cfg;
SEQCfg_Type seq_cfg;
/* Reset dtat FIFO threshold for normal amp */
if(AppCHRONOAMPCfg.EndSeq)
{
AD5940_FIFOCtrlS(FIFOSRC_SINC3, 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_SINC2NOTCH;
fifo_cfg.FIFOThresh = AppCHRONOAMPCfg.FifoThresh; /* DFT result. One pair for RCAL, another for Rz. One DFT result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
}
if(AppCHRONOAMPCfg.NumOfData > 0)
{
AppCHRONOAMPCfg.FifoDataCount += *pDataCount/4;
if(AppCHRONOAMPCfg.FifoDataCount >= AppCHRONOAMPCfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppCHRONOAMPCfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return back to controller */
static AD5940Err AppCHRONOAMPDataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t i, datacount;
datacount = *pDataCount;
float *pOut = (float *)pData;
for(i=0;i<datacount;i++)
{
pData[i] &= 0xffff;
pOut[i] = AppCHRONOAMPCalcCurrent(pData[i]);
}
return 0;
}
/**
*/
AD5940Err AppCHRONOAMPISR(void *pBuff, uint32_t *pCount)
{
extern uint32_t IntCount;
uint32_t FifoCnt;
*pCount = 0;
if(AppCHRONOAMPCfg.CHRONOAMPInited == bFALSE)
return AD5940ERR_APPERROR;
AD5940_ReadReg(REG_AFE_ADCDAT); /* Any SPI Operation can wakeup AFE */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK);
if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE)
{
FifoCnt = AD5940_FIFOGetCnt();
if(AppCHRONOAMPCfg.bMeasureTransient) /* We are measuring transient */
{
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppCHRONOAMPDataProcess((int32_t*)pBuff, &FifoCnt);
*pCount = FifoCnt;
IntCount++;
}else/* Normal Amperometric measurement*/
{
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppCHRONOAMPRegModify(pBuff, &FifoCnt); /* If there is need to do AFE re-configure, do it here when AFE is in active state */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Unlock so sequencer can put AD5940 to sleep */
AD5940_EnterSleepS();
/* Process data */
AppCHRONOAMPDataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
}
return 0;
}
if(AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_ENDSEQ) == bTRUE)/* End sequence interrupt fires at end of transient sequence */
{
AppCHRONOAMPCfg.EndSeq = bTRUE;
FifoCnt = AD5940_FIFOGetCnt();
IntCount++;
AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ);
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AppCHRONOAMPRegModify(pBuff, &FifoCnt);
/* Process data */
AppCHRONOAMPDataProcess((int32_t*)pBuff, &FifoCnt);
*pCount = FifoCnt;
}
return 0;
}
uint32_t AppCHRONOAMPCalcDataNum(uint32_t time)
{
uint32_t temp = 0;
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};
temp = time*800/sinc2osr_table[AppCHRONOAMPCfg.ADCSinc2Osr]/sinc3osr_table[AppCHRONOAMPCfg.ADCSinc3Osr];
return temp;
}
/* Calculate voltage */
float AppCHRONOAMPCalcVoltage(uint32_t ADCcode)
{
float kFactor = 1.835/1.82;
float fVolt = 0.0;
int32_t tmp = 0;
tmp = ADCcode - 32768;
switch(AppCHRONOAMPCfg.ADCPgaGain)
{
case ADCPGA_1:
fVolt = ((float)(tmp)/32768)*(AppCHRONOAMPCfg.ADCRefVolt/1)*kFactor;
break;
case ADCPGA_1P5:
fVolt = ((float)(tmp)/32768)*(AppCHRONOAMPCfg.ADCRefVolt/1.5f)*kFactor;
break;
case ADCPGA_2:
fVolt = ((float)(tmp)/32768)*(AppCHRONOAMPCfg.ADCRefVolt/2)*kFactor;
break;
case ADCPGA_4:
fVolt = ((float)(tmp)/32768)*(AppCHRONOAMPCfg.ADCRefVolt/4)*kFactor;
break;
case ADCPGA_9:
fVolt = ((float)(tmp)/32768)*(AppCHRONOAMPCfg.ADCRefVolt/9)*kFactor;
break;
}
return fVolt;
}
/* Calculate current in uA */
float AppCHRONOAMPCalcCurrent(uint32_t ADCcode)
{
float fCurrent, fVoltage = 0.0;
fVoltage = AppCHRONOAMPCalcVoltage(ADCcode);
fCurrent = fVoltage/AppCHRONOAMPCfg.RtiaCalValue.Magnitude;
return -fCurrent*1000000;
}

View File

@ -0,0 +1,111 @@
/*!
*****************************************************************************
@file: ChronoAmperometric.h
@author: $Author: nxu2 $
@brief: ChronoAmperometric measurement header file.
@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.
*****************************************************************************/
#ifndef _CHRONOAMPEROMETRIC_H_
#define _CHRONOAMPEROMETRIC_H_
#include "ad5940.h"
#include "stdio.h"
#include "string.h"
#include "math.h"
#define DAC12BITVOLT_1LSB (2200.0f/4095) //mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) //mV
/*
Note: this example will use SEQID_0 as measurement sequence, and use SEQID_1 as init sequence.
SEQID_3 is used for calibration.
*/
typedef struct
{
/* Common configurations for all kinds of Application. */
BoolFlag bParaChanged; /* Indicate to generate sequence again. It's auto cleared by AppCHRONOAMPInit */
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 */
BoolFlag ReDoRtiaCal; /* Set this flag to bTRUE when there is need to do calibration. */
float SysClkFreq; /* The real frequency of system clock */
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 AdcClkFreq; /* The real frequency of ADC clock */
uint32_t FifoThresh; /* FIFO threshold. Should be N*4 */
float AmpODR; /* in Hz. ODR decides the period of WakeupTimer who will trigger sequencer periodically. DFT number and sample frequency decides the maxim ODR. */
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 RcalVal; /* Rcal value in Ohm */
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
/* Receive path configuration */
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; /* SINC3 OSR selection. ADCSINC3OSR_2, ADCSINC3OSR_4 */
uint8_t ADCSinc2Osr; /* SINC2 OSR selection. ADCSINC2OSR_22...ADCSINC2OSR_1333 */
uint32_t DataFifoSrc; /* DataFIFO source. DATATYPE_ADCRAW, DATATYPE_SINC3 or DATATYPE_SINC2*/
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 */
fImpPol_Type RtiaCalValue; /* Calibrated Rtia value */
BoolFlag ExtRtia; /* Use internal or external Rtia */
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 CtiaSel; /* Select CTIA in pF unit from 0 to 31pF */
/* LPDAC Config */
float Vzero; /* Voltage on SE0 pin and Vzero*/
float Vbias; /* Voltage on CE0 and PA */
float SensorBias; /* Sensor bias voltage = VRE0 - VSE0 */
float pulseAmplitude; /* High voltage level */
float voltL; /* Low voltage level */
float ADCRefVolt; /*Vref value */
float ExtRtiaVal; /* External Rtia value if using one */
uint32_t pulseLength; /* length of transient measure */
BoolFlag EndSeq;
BoolFlag bMeasureTransient;
BoolFlag CHRONOAMPInited; /* If the program run firstly, generated sequence commands */
SEQInfo_Type InitSeqInfo;
SEQInfo_Type MeasureSeqInfo;
SEQInfo_Type TransientSeqInfo;
BoolFlag StopRequired; /* After FIFO is ready, stop the measurement sequence */
uint32_t FifoDataCount; /* Count how many times impedance have been measured */
/* End */
}AppCHRONOAMPCfg_Type;
/**
* int32_t type Impedance result in Cartesian coordinate
*/
typedef struct
{
float Current;
float Voltage;
}fAmpRes_Type;
#define CHRONOAMPCTRL_START 0
#define CHRONOAMPCTRL_STOPNOW 1
#define CHRONOAMPCTRL_STOPSYNC 2
#define CHRONOAMPCTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
#define CHRONOAMPCTRL_PULSETEST 5
AD5940Err AppCHRONOAMPGetCfg(void *pCfg);
AD5940Err AppCHRONOAMPInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppCHRONOAMPISR(void *pBuff, uint32_t *pCount);
AD5940Err AppCHRONOAMPCtrl(int32_t AmpCtrl, void *pPara);
uint32_t AppCHRONOAMPCalcDataNum(uint32_t time);
float AppCHRONOAMPCalcVoltage(uint32_t ADCcode);
float AppCHRONOAMPCalcCurrent(uint32_t ADCcode);
#endif

View File

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

View File

@ -0,0 +1,64 @@
/*!
*****************************************************************************
@file: Electrocardiograph.h
@author: Neo Xu
@brief: ECG measurement.
-----------------------------------------------------------------------------
Copyright (c) 2017-2019 Analog Devices, Inc. All Rights Reserved.
This software is proprietary to Analog Devices, Inc. and its licensors.
By using this software you agree to the terms of the associated
Analog Devices Software License Agreement.
*****************************************************************************/
#ifndef _ELETROCARDIOAGRAPH_H_
#define _ELETROCARDIOAGRAPH_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 */
BoolFlag bBioElecBoard; /* Indicate if the board is Bioelec board. 0: AD5941Sens1 board, 1: AD5940-BioElec */
uint32_t SeqStartAddr; /* Initialaztion sequence start address in SRAM of AD5940 */
uint32_t MaxSeqLen; /* Limit the maximum sequence. */
uint32_t SeqStartAddrCal; /* Calibration sequence start address in SRAM of AD5940 */
uint32_t MaxSeqLenCal;
/* Application related parameters */
float ECGODR; /* Must be less than 1500Hz. Sample frequency in Hz, this value is used to set Sleep Wakeup Timer period */
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. */
uint32_t FifoThresh; /* FIFO threshold. Should be N*4 */
float LfoscClkFreq; /* The clock frequency of internal LFOSC 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 */
uint32_t PwrMod; /* Control Chip power mode(LP/HP) */
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 */
uint32_t ADCSinc3Osr;
uint32_t ADCSinc2Osr;
/* Private variables for internal usage */
BoolFlag ECGInited; /* If the program run firstly, generated sequence commands */
BoolFlag StopRequired; /* After FIFO is ready, stop the measurement sequence */
uint32_t FifoDataCount; /* How many data we have got from start. */
SEQInfo_Type InitSeqInfo;
SEQInfo_Type MeasureSeqInfo;
}AppECGCfg_Type;
#define APPCTRL_START 0
#define APPCTRL_STOPNOW 1
#define APPCTRL_STOPSYNC 2
//#define APPCTRL_GETFREQ 3
#define APPCTRL_SHUTDOWN 4 /* Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
AD5940Err AppECGGetCfg(void *pCfg);
AD5940Err AppECGInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppECGISR(void *pBuff, uint32_t *pCount);
AD5940Err AppECGCtrl(int32_t Command, void *pPara);
#endif

View File

@ -0,0 +1,874 @@
/*!
*****************************************************************************
@file: ElectrodermalActivity.c
@author: Neo Xu
@brief: EDA 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 "ElectrodermalActivity.h"
/** @addtogroup AD5940_System_Examples
* @{
* @defgroup EDA_Example
* @brief This example is used to measure skin impedance. The main feature of this example is ultra low power consumption.
* @details
* @note Need to update code when runs at S2 silicon.
*
*
*
* @{
* */
/**
* @brief The EDA application paramters.
* @details Do not modify following default parameters. Use the function in AD5940Main.c to change it.
*
* */
AppEDACfg_Type AppEDACfg =
{
/* Common configurations for all kinds of Application. */
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
/* Application related parameters */
.bBioElecBoard = bTRUE,
.ReDoRtiaCal = bFALSE,
.SysClkFreq = 16000000.0,
.LfoscClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.FifoThresh = 4,
.EDAODR = 4.0, /* 20.0 Hz*/
.NumOfData = -1,
.VoltCalPoints = 8,
.RcalVal = 10000.0, /* 10kOhm */
.SinFreq = 100.0, /* 100Hz */
.SampleFreq = 400.0, /* 400Hz */
.SinAmplitude = 1100.0f/2, /* 1100mV peak */
.DacUpdateRate = 7,
.LptiaRtiaSel = LPTIARTIA_100K,
.DftNum = DFTNUM_16,
.HanWinEn = bTRUE,
.RtiaAutoScaleEnable = bTRUE,
.RtiaAutoScaleMax = LPTIARTIA_512K,
.RtiaAutoScaleMin = LPTIARTIA_1K,
.RtiaIndexCurr = 0,
.RtiaIndexNext = 0,
.bChangeRtia = bFALSE,
/* private varaibles */
.SeqPatchInfo ={
.BuffLen = 32,
.pSeqCmd = NULL,
},
.ImpEDABase = {0,0},
.ImpSum = {0,0},
.EDAInited = bFALSE,
.StopRequired = bFALSE,
.bRunning = bFALSE,
.bMeasVoltReq = bFALSE,
.EDAStateCurr = EDASTATE_INIT,
.EDAStateNext = EDASTATE_INIT,
};
/**
* @brief This function is provided for upper controllers that want to change
* application parameters specially for user defined parameters.
* @param pCfg: The pointer used to store application configuration structure pointer.
* @return none.
*/
AD5940Err AppEDAGetCfg(void *pCfg)
{
if(pCfg){
*(AppEDACfg_Type**)pCfg = &AppEDACfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
/**
* @brief Control application like start, stop.
* @param Command: The command for this application, select from below paramters
* - APPCTRL_START: start the measurement. Note: the ramp test need firstly call function AppRAMPInit() every time before start it.
* - APPCTRL_STOPNOW: Stop the measurement immediately.
* - APPCTRL_STOPSYNC: Stop the measuremnt when current measured data is read back.
* - APPCTRL_SHUTDOWN: Stop the measurement immediately and put AFE to shut down mode(turn off LP loop and enter hibernate).
* - EDACTRL_MEASVOLT: Measure voltage once current measurement is done(Interrupt occurred).
* - EDACTRL_GETRTIAMAG: Get current RTIA value.
* @return none.
*/
AD5940Err AppEDACtrl(int32_t EDACtrl, void *pPara)
{
switch (EDACtrl)
{
case APPCTRL_START:
{
WUPTCfg_Type wupt_cfg;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if(AppEDACfg.EDAInited == 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] = (uint32_t)(AppEDACfg.LfoscClkFreq/AppEDACfg.EDAODR)-2-4;
wupt_cfg.SeqxWakeupTime[SEQID_0] = 4; /* The minimum value is 1. Do not set it to zero. Set it to 1 will spend 2 32kHz clock. */
AD5940_WUPTCfg(&wupt_cfg);
AppEDACfg.FifoDataCount = 0; /* restart */
AppEDACfg.bRunning = bTRUE;
break;
}
case APPCTRL_STOPNOW:
{
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Start Wupt right now */
AD5940_WUPTCtrl(bFALSE);
/* There is chance this operation will fail because sequencer could put AFE back
to hibernate mode just after waking up. Use STOPSYNC is better. */
AD5940_WUPTCtrl(bFALSE);
AppEDACfg.bRunning = bFALSE;
break;
}
case APPCTRL_STOPSYNC:
{
AppEDACfg.StopRequired = bTRUE;
break;
}
case APPCTRL_SHUTDOWN:
{
AppEDACtrl(APPCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
/* Turn off LPLoop related blocks which are not controlled automatically by hibernate operation */
AFERefCfg_Type aferef_cfg;
LPLoopCfg_Type lp_loop;
memset(&aferef_cfg, 0, sizeof(aferef_cfg));
AD5940_REFCfgS(&aferef_cfg);
memset(&lp_loop, 0, sizeof(lp_loop));
AD5940_LPLoopCfgS(&lp_loop);
AD5940_EnterSleepS(); /* Enter Hibernate */
}
break;
case EDACTRL_MEASVOLT:
AppEDACfg.bMeasVoltReq = bTRUE;
break;
case EDACTRL_GETRTIAMAG:
if(pPara == NULL)
return AD5940ERR_NULLP; /* Null pointer */
*(float*)pPara = AD5940_ComplexMag(&AppEDACfg.RtiaCurrValue);
break;
case EDACTRL_RSTBASE:
AppEDACfg.ImpEDABase.Real = 0;
AppEDACfg.ImpEDABase.Image = 0;
AppEDACfg.ImpSum.Real = 0;
AppEDACfg.ImpSum.Image = 0;
AppEDACfg.ImpSumCount = 0;
break;
case EDACTRL_SETBASE:
{
fImpCar_Type *pImpBase = (fImpCar_Type *)pPara; /* The impedance used to set base line */
AppEDACfg.ImpEDABase = *pImpBase;
}
break;
case EDACTRL_GETAVR:
if(pPara == NULL) return AD5940ERR_NULLP;
{
fImpCar_Type *pImpAVR = (fImpCar_Type *)pPara;
pImpAVR->Real = AppEDACfg.ImpSum.Real/AppEDACfg.ImpSumCount;
pImpAVR->Image = AppEDACfg.ImpSum.Image/AppEDACfg.ImpSumCount;
break;
}
case APPCTRL_RUNNING:
case EDACTRL_STATUS:
if(pPara == NULL)
return AD5940ERR_NULLP; /* Null pointer */
*(BoolFlag*)pPara = AppEDACfg.bRunning;
break;
default:
break;
}
return AD5940ERR_OK;
}
/**
* @brief Generate initialization sequence and write the commands to SRAM.
* @return return error code.
*/
static AD5940Err AppEDASeqCfgGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSDACCfg_Type hsdac_cfg; /* Waveform Generator uses some parameter(DAC update rate) from HSDAC config registers */
LPLoopCfg_Type lp_loop;
WGCfg_Type wg_cfg;
DSPCfg_Type dsp_cfg;
SWMatrixCfg_Type sw_cfg;
AD5940_SEQGenCtrl(bTRUE);
/* Sequence starts here */
AD5940_SEQGpioCtrlS(AGPIO_Pin6/*|AGPIO_Pin5*/|AGPIO_Pin1);
AD5940_StructInit(&aferef_cfg, sizeof(aferef_cfg));
AD5940_REFCfgS(&aferef_cfg); /* Turn off all references, we only enable it when we need it. */
AD5940_StructInit(&lp_loop, sizeof(lp_loop)); /* Disable everything, configure them during measurement */
AD5940_LPLoopCfgS(&lp_loop);
AD5940_StructInit(&wg_cfg, sizeof(wg_cfg));
wg_cfg.SinCfg.SinAmplitudeWord = (uint32_t)(AppEDACfg.SinAmplitude/1100.0f*2047); /* Maximum amplitude is 1100mV */
wg_cfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(AppEDACfg.SinFreq, AppEDACfg.LfoscClkFreq);
wg_cfg.SinCfg.SinPhaseWord = 0;
wg_cfg.WgType = WGTYPE_SIN;
AD5940_WGCfgS(&wg_cfg);
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
/* Switch configuration for BioElec board */
sw_cfg.Dswitch = SWD_OPEN; /* Open all switch D */
sw_cfg.Pswitch = SWP_AIN2|SWP_SE0;
sw_cfg.Nswitch = SWN_OPEN;
sw_cfg.Tswitch = SWT_AIN0|SWT_AFE3LOAD;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_StructInit(&dsp_cfg, sizeof(dsp_cfg));
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_VSET1P1;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_AIN4;
dsp_cfg.ADCBaseCfg.ADCPga = ADCPGA_1;
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ;
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_4; /* We use averaged SINC3 output as DFT input source */
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = ADCSINC2OSR_22; /* Don't care */
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = ADCSINC3OSR_5;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bFALSE;
dsp_cfg.DftCfg.DftNum = AppEDACfg.DftNum;
dsp_cfg.DftCfg.DftSrc = DFTSRC_AVG; /* Use averaged SINC3 data */
dsp_cfg.DftCfg.HanWinEn = AppEDACfg.HanWinEn;
AD5940_DSPCfgS(&dsp_cfg);
AD5940_ADCRepeatCfgS(5*(4+2)+1); /* (n+2)*osr + 1, n=4,osr=5*/
hsdac_cfg.ExcitBufGain = EXCITBUFGAIN_2;
hsdac_cfg.HsDacGain = HSDACGAIN_1;
hsdac_cfg.HsDacUpdateRate = AppEDACfg.DacUpdateRate; /* Note: the DAC update rate is decided by register DACON.RATE */
AD5940_HSDacCfgS(&hsdac_cfg);
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
/* 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 */
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
if(error == AD5940ERR_OK)
{
AppEDACfg.InitSeqInfo.SeqId = SEQID_1;
AppEDACfg.InitSeqInfo.SeqRamAddr = AppEDACfg.SeqStartAddr;
AppEDACfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppEDACfg.InitSeqInfo.SeqLen = SeqLen;
AppEDACfg.InitSeqInfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&AppEDACfg.InitSeqInfo); /* Write command to SRAM */
}
else
return error; /* Error */
return AD5940ERR_OK;
}
/**
* @brief Generate patch sequence according to current measurement type(Voltage or Current).
* @details The patch is used to adjust sequencer commands already stored in SRAM of AD5940 in order to perform different measurements.
* The reason is that the sequences need to be adjusted. Using the patch method will make things easily and we won't need to modify
* sequences in register level.
* @param pPatch: pointer to patch information include the measurement type, Rtia selection and buffers.
* @return return error code.
*/
static AD5940Err ApPEDASeqPatchGen(SeqPatchInfo_Type *pPatch)
{
AD5940Err err;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
LPAmpCfg_Type lpamp_cfg;
AD5940_SEQGenInit(pPatch->Buffer, pPatch->BuffLen);
AD5940_SEQGenCtrl(bTRUE);
lpamp_cfg.LpAmpSel = LPAMP0;
lpamp_cfg.LpAmpPwrMod = LPAMPPWR_NORM; /* Use normal power mode is enough */
lpamp_cfg.LpPaPwrEn = bTRUE; /* Enable Potential amplifier */
lpamp_cfg.LpTiaPwrEn = bTRUE; /* Enable TIA amplifier */
lpamp_cfg.LpTiaRf = LPF_RF; /* Rf resistor controls cut off frequency. */
lpamp_cfg.LpTiaRload = LPTIARLOAD_100R; /** @note Use 100Ohm Rload. */
lpamp_cfg.LpTiaRtia = pPatch->RtiaSel; /* If autoscaling is enabled, use selected value. */
if(pPatch->Type == PATCHTYPE_VOLT)
lpamp_cfg.LpTiaSW = LPTIASW_VOLT; /* Switch settings for voltage measurement */
else if(pPatch->Type == PATCHTYPE_CURR)
lpamp_cfg.LpTiaSW = LPTIASW_CURR; /* Switch settings for current measurement */
AD5940_LPAMPCfgS(&lpamp_cfg);
AD5940_SEQGenCtrl(bFALSE);
err = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
if(err != AD5940ERR_OK)
return err;
pPatch->pSeqCmd = pSeqCmd;
pPatch->SeqLen = SeqLen;
return AD5940ERR_OK;
}
/**
* @brief Generate measurement sequence and write the commands to SRAM.
* @return return error code.
*/
static AD5940Err AppEDASeqMeasureGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
uint32_t i;
uint32_t DFTNumber;
LPDACCfg_Type lpdac_cfg;
LPAmpCfg_Type lpamp_cfg;
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
/* Stage I: Initialization */
AD5940_SEQGpioCtrlS(AGPIO_Pin6/*|AGPIO_Pin5|AGPIO_Pin1*/);//GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
/* LP loop configure: LPDAC and LPAMP */
lpdac_cfg.LpdacSel = LPDAC0;
lpdac_cfg.DataRst = bFALSE;
lpdac_cfg.LpDacSW = LPDACSW_VBIAS2LPPA/*|LPDACSW_VBIAS2PIN*/|LPDACSW_VZERO2LPTIA|LPDACSW_VZERO2PIN;
lpdac_cfg.LpDacRef = LPDACREF_2P5; /* Use internal 2.5V reference */
lpdac_cfg.LpDacSrc = LPDACSRC_WG; /* Use data from waveform generator */
lpdac_cfg.LpDacVbiasMux = LPDACVBIAS_12BIT;
lpdac_cfg.LpDacVzeroMux = LPDACVZERO_6BIT; /* Use 6bit LPDAC for Vzero */
lpdac_cfg.PowerEn = bTRUE; /* Enable LPDAC */
lpdac_cfg.DacData12Bit = 0; /* Don't care, 12bit DAC data is from WG */
lpdac_cfg.DacData6Bit = 32; /* Set it to middle scale of LPDAC. Vzero is the bias voltage of LPTIA amplifier */
AD5940_LPDACCfgS(&lpdac_cfg);
/* Voltage and current measurement need different switch settings, record the difference and only modify this part for different purpose */
error = AD5940_SEQGenFetchSeq(NULL, &AppEDACfg.SeqPatchInfo.SRAMAddr); /* Record the start address of below commands */
if(error != AD5940ERR_OK)
return error;
lpamp_cfg.LpAmpSel = LPAMP0;
lpamp_cfg.LpAmpPwrMod = LPAMPPWR_NORM; /* Use normal power mode is enough */
lpamp_cfg.LpPaPwrEn = bTRUE; /* Enable Potential amplifier */
lpamp_cfg.LpTiaPwrEn = bTRUE; /* Enable TIA amplifier */
lpamp_cfg.LpTiaRf = LPF_RF; /* Rf resistor controls cut off frequency. */
lpamp_cfg.LpTiaRload = LPTIARLOAD_100R; /** @note Use 100Ohm Rload. */
lpamp_cfg.LpTiaRtia = AppEDACfg.LptiaRtiaSel; /* If autoscaling is enabled, use seleted value. */
lpamp_cfg.LpTiaSW = LPTIASW_VOLT; /* Swtich settings for voltage measurement */
AD5940_LPAMPCfgS(&lpamp_cfg);
AD5940_WriteReg(REG_AFE_LPREFBUFCON, 0); /* Enable low power bandgap and 2.5V reference buffer */
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Off everything */
AD5940_LPModeEnS(bTRUE); /* Enter LP control mode. The registers are summarized to LPMODECON, so we can control some blocks convenniently */
AD5940_LPModeClkS(LPMODECLK_LFOSC); /* Trigger switching system clock to 32kHz */
AD5940_LPModeCtrlS(LPMODECTRL_NONE); /* Disable all */
AD5940_AFECtrlS(AFECTRL_WG, bTRUE); /* Start waveform generator */
AD5940_SEQGenInsert(SEQ_WAIT(LPF_TIME*32)); /* wait for stable */
AD5940_AFECtrlS(AFECTRL_DFT, bTRUE); /* Enable DFT engine */
/* Stage II: ADC Run to sample enough data(DFT number) */
DFTNumber = (1<<(AppEDACfg.DftNum +2));
for(i=0;i<DFTNumber;i++)
{
#define EDA_LPMODCTRLSETS LPMODECTRL_GLBBIASZ|LPMODECTRL_GLBBIASP|LPMODECTRL_HPREFPWR|LPMODECTRL_BUFHP1P8V|LPMODECTRL_BUFHP1P1V|LPMODECTRL_HFOSCEN
AD5940_LPModeCtrlS(EDA_LPMODCTRLSETS); /* Turn ON HPREF. */
AD5940_SEQGenInsert(SEQ_WAIT(4));
AD5940_LPModeCtrlS(EDA_LPMODCTRLSETS|LPMODECTRL_REPEATEN); /* Set RepeatEN will enable ADC power */
AD5940_SEQGenInsert(SEQ_NOP()); /* Wait 50us at least to allow ADC settiling. one NOP commands consumes two system clock(32kHz) before ADCCNV = 61.5us. */
AD5940_LPModeCtrlS(EDA_LPMODCTRLSETS|LPMODECTRL_ADCCNV); /* Start ADC conversion. !!Clear REPEATEN bit */
AD5940_SEQGenInsert(SEQ_NOP());
/* One command need 31.25us because the system clock is 32kHz now. */
if(i != DFTNumber-1) /* There is no need to wait such long time for last point, only enough clock for DFT calculation before disable it. */
{
AD5940_LPModeCtrlS(LPMODECTRL_NONE); /* Disable all */
AD5940_SEQGenInsert(SEQ_WAIT(AppEDACfg.LfoscClkFreq/AppEDACfg.SampleFreq - 12));
}
else
{
AD5940_LPModeCtrlS(LPMODECTRL_HFOSCEN); /* Disable all except 16MHz HFOSC */
AD5940_SEQGenInsert(SEQ_WAIT(21)); /* wait another 21 clocks. DFT need it to calculte last input data */
}
}
/* Stage III: Turn off all we can */
AD5940_LPModeClkS(LPMODECLK_HFOSC); /* Switching back to 16MHz */
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Disable waveform generator */
lpamp_cfg.LpAmpSel = LPAMP0;
lpamp_cfg.LpPaPwrEn = bFALSE;
lpamp_cfg.LpTiaPwrEn = bFALSE;
lpamp_cfg.LpTiaRf = LPTIARF_OPEN;
lpamp_cfg.LpTiaRtia = LPTIARTIA_OPEN;
lpamp_cfg.LpTiaSW = 0;
AD5940_LPAMPCfgS(&lpamp_cfg);
AD5940_WriteReg(REG_AFE_LPREFBUFCON, BITM_AFE_LPREFBUFCON_LPBUF2P5DIS|BITM_AFE_LPREFBUFCON_LPREFDIS);
lpdac_cfg.LpDacSW = 0; /* Disconnect all switch */
lpdac_cfg.PowerEn = bFALSE;
AD5940_LPDACCfgS(&lpdac_cfg);
AD5940_SEQGpioCtrlS(0/*AGPIO_Pin6|AGPIO_Pin5|AGPIO_Pin1*/); //GP6->endSeq, GP5 -> AD8233=OFF, GP1->RLD=OFF .
AD5940_EnterSleepS();/* Go to hibernate */
/* Sequence end. */
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
if(error == AD5940ERR_OK)
{
if(AppEDACfg.MaxSeqLen < (SeqLen + AppEDACfg.InitSeqInfo.SeqLen))
return AD5940ERR_BUFF; /* Buffer limited */
AppEDACfg.MeasureSeqInfo.SeqId = SEQID_0;
AppEDACfg.MeasureSeqInfo.SeqRamAddr = AppEDACfg.InitSeqInfo.SeqRamAddr + AppEDACfg.InitSeqInfo.SeqLen ;
AppEDACfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppEDACfg.MeasureSeqInfo.SeqLen = SeqLen;
AppEDACfg.MeasureSeqInfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&AppEDACfg.MeasureSeqInfo); /* Write command to SRAM */
/* Record where the patch should be applied. */
AppEDACfg.SeqPatchInfo.SRAMAddr += AppEDACfg.MeasureSeqInfo.SeqRamAddr; /* The start address in AD5940 SRAM */
}
else
return error; /* Error */
return AD5940ERR_OK;
}
/**
* @brief Calibrate LPTIA internal RTIA resistor(s).
* @details This function will do calibration using parameters stored in @ref AppEDACfg structure.
* @return return error code.
*/
static AD5940Err AppEDARtiaCal(void)
{
fImpCar_Type RtiaCalValue; /* Calibration result */
LPRTIACal_Type lprtia_cal;
AD5940_StructInit(&lprtia_cal, sizeof(lprtia_cal));
lprtia_cal.LpAmpSel = LPAMP0;
lprtia_cal.bPolarResult = bFALSE; /* Real + Image */
lprtia_cal.AdcClkFreq = AppEDACfg.AdcClkFreq;
lprtia_cal.SysClkFreq = AppEDACfg.SysClkFreq;
lprtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
lprtia_cal.ADCSinc2Osr = ADCSINC2OSR_22; /* We don't use SINC2 for now. */
lprtia_cal.DftCfg.DftNum = DFTNUM_2048; /* Maximum DFT number */
lprtia_cal.DftCfg.DftSrc = DFTSRC_SINC2NOTCH;
lprtia_cal.DftCfg.HanWinEn = bTRUE;
lprtia_cal.fFreq = AppEDACfg.SinFreq;
lprtia_cal.fRcal = AppEDACfg.RcalVal;
lprtia_cal.bWithCtia = bTRUE;
lprtia_cal.LpAmpPwrMod = LPAMPPWR_NORM;
lprtia_cal.bWithCtia = bTRUE;
lprtia_cal.LpTiaRtia = AppEDACfg.LptiaRtiaSel;
if(AppEDACfg.RtiaAutoScaleEnable == bTRUE)
{
int i = AppEDACfg.RtiaAutoScaleMin;
for(;i<=AppEDACfg.RtiaAutoScaleMax; i++)
{
lprtia_cal.LpTiaRtia = i;
AD5940_LPRtiaCal(&lprtia_cal, &RtiaCalValue);
AppEDACfg.RtiaCalTable[i] = RtiaCalValue;
//printf("Rtia%d,%f,%f\n", i, RtiaCalValue.Real, RtiaCalValue.Image);
}
AppEDACfg.RtiaCurrValue = AppEDACfg.RtiaCalTable[AppEDACfg.RtiaIndexCurr];
}
else
{
AD5940_LPRtiaCal(&lprtia_cal, &RtiaCalValue);
AppEDACfg.RtiaCurrValue = RtiaCalValue;
//printf("Rtia,%f,%f\n", RtiaCalValue.Real, RtiaCalValue.Image);
//printf("Rtia calibration done\n");
}
return AD5940ERR_OK;
}
/**
* @brief Initialize the EDA measurement.
* @details This function must be called before start measurement. It will initialize all needed hardwares and put AD5940 to ready state.
* The application parameters stored in @ref AppEDACfg can be changed. Call this function to re-initialize AD5940 with new parameters.
* @param pBuffer: the buffer for sequencer generator. Only need to provide it for the first time.
* @param BufferSize: The buffer size start from pBuffer.
* @return return error code.
*/
AD5940Err AppEDAInit(uint32_t *pBuffer, uint32_t BufferSize)
{
AD5940Err error = AD5940ERR_OK;
SEQCfg_Type seq_cfg;
FIFOCfg_Type fifo_cfg;
AppEDACfg.EDAStateCurr = EDASTATE_INIT;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Configure sequencer and stop it */
seq_cfg.SeqMemSize = SEQMEMSIZE_2KB;
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Do RTIA calibration */
if((AppEDACfg.ReDoRtiaCal == bTRUE) || \
AppEDACfg.EDAInited == bFALSE) /* Do calibration on the first initialization */
{
AppEDACfg.EDAStateCurr = EDASTATE_RTIACAL;
AppEDARtiaCal();
AppEDACfg.ReDoRtiaCal = bFALSE;
//AppEDAMeasureRserial();
}
/* 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 = AppEDACfg.VoltCalPoints*2; /* The first measurement is for excitation voltage. */
AD5940_FIFOCfg(&fifo_cfg);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Start sequence generator */
/* Initialize sequencer generator */
if((AppEDACfg.EDAInited == bFALSE)||\
(AppEDACfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppEDASeqCfgGen(); /* Application initialization sequence using either MCU or sequencer */
if(error != AD5940ERR_OK) return error;
/* Generate measurement sequence */
error = AppEDASeqMeasureGen();
if(error != AD5940ERR_OK) return error;
AppEDACfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Initialization sequence */
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer, run initialization sequence */
AD5940_SEQMmrTrig(AppEDACfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
/* Apply patch for voltage measurement */
AppEDACfg.EDAStateCurr = EDASTATE_VOLT; /* After initialization, the first thing is to measure excitation voltage */
AppEDACfg.RtiaIndexCurr = AppEDACfg.RtiaIndexNext = AppEDACfg.LptiaRtiaSel; /* Init with a value */
AppEDACfg.SeqPatchInfo.RtiaSel = LPTIARTIA_OPEN;//AppEDACfg.RtiaIndexCurr;
//AppEDACfg.SeqPatchInfo.bMeasureVolt = bTRUE;
AppEDACfg.SeqPatchInfo.Type = PATCHTYPE_VOLT;
error = ApPEDASeqPatchGen(&AppEDACfg.SeqPatchInfo);
if(error != AD5940ERR_OK)
return error;
AD5940_SEQCmdWrite(AppEDACfg.SeqPatchInfo.SRAMAddr, \
AppEDACfg.SeqPatchInfo.pSeqCmd, AppEDACfg.SeqPatchInfo.SeqLen); /* Apply the patch to SRAM */
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
AD5940_WriteReg(REG_AFE_SWMUX, 0x01); /**@todo remove it? close switch SW1 */
if(AppEDACfg.RtiaAutoScaleMin > AppEDACfg.RtiaAutoScaleMax)
{
uint32_t temp;
temp = AppEDACfg.RtiaAutoScaleMin;
AppEDACfg.RtiaAutoScaleMin = AppEDACfg.RtiaAutoScaleMax;
AppEDACfg.RtiaAutoScaleMax = temp;
}
AppEDACfg.EDAInited = bTRUE; /* EDA application has been initialized. */
return AD5940ERR_OK;
}
/**
* @brief Register modification function.
* @details This function is called in ISR when AFE has been wakeup and we can access registers.
* @param pData: the buffer points to data read back from FIFO. Not needed for this application-RAMP
* @param pDataCount: The data count in pData buffer.
* @return return error code.
*/
static AD5940Err AppEDARegModify(int32_t * const pData, uint32_t *pDataCount)
{
AD5940Err error = AD5940ERR_OK;
if(AppEDACfg.EDAStateCurr == EDASTATE_VOLT)
{
SWMatrixCfg_Type sw_cfg;
/* Next step is to measure current */
AppEDACfg.EDAStateNext = EDASTATE_CURR;
/* Need change some registers in order to measure current */
AD5940_SEQCtrlS(bFALSE); /* Stop it for now. */
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE); /* Disable FIFO firstly because we are going to change FIFO threshold */
AD5940_FIFOThrshSet(AppEDACfg.FifoThresh);
AD5940_FIFOCtrlS(FIFOSRC_DFT, bTRUE); /* Enable FIFO. */
/* Change Switch matrix settings to connect AIN2(body) to SE0 */
sw_cfg.Dswitch = SWD_OPEN; /* Open all switch D */
sw_cfg.Pswitch = SWP_AIN2|SWP_SE0;
sw_cfg.Nswitch = SWN_OPEN;
sw_cfg.Tswitch = SWT_AIN0|SWT_AFE3LOAD; /* This switch is for ECG. */
AD5940_SWMatrixCfgS(&sw_cfg);
/* Apply patch for current measurement */
//AppEDACfg.SeqPatchInfo.bMeasureVolt = bFALSE;
AppEDACfg.SeqPatchInfo.Type = PATCHTYPE_CURR;
AppEDACfg.SeqPatchInfo.RtiaSel = AppEDACfg.RtiaIndexNext;
error = ApPEDASeqPatchGen(&AppEDACfg.SeqPatchInfo);
if(error != AD5940ERR_OK)
return error;
AD5940_SEQCmdWrite(AppEDACfg.SeqPatchInfo.SRAMAddr, \
AppEDACfg.SeqPatchInfo.pSeqCmd, AppEDACfg.SeqPatchInfo.SeqLen); /* Apply the patch to SRAM */
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer. Sequencer will run when next valid trigger comes */
}
else if(AppEDACfg.EDAStateCurr == EDASTATE_CURR)
{
if(AppEDACfg.bChangeRtia == bTRUE)
{
AppEDACfg.bChangeRtia = bFALSE;
/* Apply patch for next RTIA selection */
AppEDACfg.SeqPatchInfo.Type = PATCHTYPE_CURR;
AppEDACfg.SeqPatchInfo.RtiaSel = AppEDACfg.RtiaIndexNext;
error = ApPEDASeqPatchGen(&AppEDACfg.SeqPatchInfo);
if(error != AD5940ERR_OK)
return error;
AD5940_SEQCmdWrite(AppEDACfg.SeqPatchInfo.SRAMAddr, \
AppEDACfg.SeqPatchInfo.pSeqCmd, AppEDACfg.SeqPatchInfo.SeqLen); /* Apply the patch to SRAM */
}
}
if(AppEDACfg.bMeasVoltReq == bTRUE)
{
SWMatrixCfg_Type sw_cfg;
AppEDACfg.bMeasVoltReq = bFALSE; /* Clear this request */
/* Next step is to measure voltage */
AppEDACfg.EDAStateNext = EDASTATE_VOLT;
/* Change Switch matrix settings to connect AIN2(body) to SE0 */
sw_cfg.Dswitch = SWD_OPEN; /* Open all switch D */
sw_cfg.Pswitch = SWP_OPEN;
sw_cfg.Nswitch = SWN_OPEN;
sw_cfg.Tswitch = SWT_AIN0|SWT_AFE3LOAD; /* This switch is for ECG. */
AD5940_SWMatrixCfgS(&sw_cfg);
/* Need change some registers in order to measure current */
AD5940_SEQCtrlS(bFALSE); /* Stop it for now. */
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE); /* Disable FIFO firstly because we are going to change FIFO threshold */
AD5940_FIFOThrshSet(AppEDACfg.VoltCalPoints*2);
AD5940_FIFOCtrlS(FIFOSRC_DFT, bTRUE); /* Enable FIFO. */
/* Apply patch for current measurement */
AppEDACfg.SeqPatchInfo.Type = PATCHTYPE_VOLT;
AppEDACfg.SeqPatchInfo.RtiaSel = LPTIARTIA_OPEN;//AppEDACfg.RtiaIndexNext;
error = ApPEDASeqPatchGen(&AppEDACfg.SeqPatchInfo);
if(error != AD5940ERR_OK)
return error;
AD5940_SEQCmdWrite(AppEDACfg.SeqPatchInfo.SRAMAddr, \
AppEDACfg.SeqPatchInfo.pSeqCmd, AppEDACfg.SeqPatchInfo.SeqLen); /* Apply the patch to SRAM */
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer. Sequencer will run when next valid trigger comes */
}
if(AppEDACfg.NumOfData > 0)
{
AppEDACfg.FifoDataCount += *pDataCount/4;
if(AppEDACfg.FifoDataCount >= AppEDACfg.NumOfData)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if(AppEDACfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
AppEDACfg.StopRequired = bFALSE;
AppEDACfg.bRunning = bFALSE;
return AD5940ERR_OK;
}
return AD5940ERR_OK;
}
/**
* @brief Depending on the data type, do appropriate data pre-process before return back to controller
* @param pImpedance: the buffer points to pre-processed data. We use the impedance magnitude value to decide new RTIA settings.
* @param uiDataCount: The data count in pData buffer.
* @return return the next appropriate RTIA index value.
*/
static uint32_t EDARtiaAutoScaling(fImpCar_Type * const pImpedance, uint32_t uiDataCount)
{
uint32_t OptRtiaIndex;
float MagMean = 0;
fImpCar_Type SumImp={0,0};
/* Get Averaged Magnitude Result */
for(int i=0;i<uiDataCount;i++)
{
SumImp.Real += pImpedance[i].Real;
SumImp.Image += pImpedance[i].Image;
}
SumImp.Real /= uiDataCount;
SumImp.Image /= uiDataCount;
SumImp = AD5940_ComplexAddFloat(&SumImp, &AppEDACfg.ImpEDABase); /* Impedance under test is sum of changed value and baseline */
MagMean = AD5940_ComplexMag(&SumImp);
OptRtiaIndex = AppEDACfg.RtiaAutoScaleMin;
/* This is much easier because although the RTIA is not the best value, the results are still reliable. We can directly choose the RTIA matched */
for(;OptRtiaIndex < AppEDACfg.RtiaAutoScaleMax;)
{
float mag;
mag = AD5940_ComplexMag(&AppEDACfg.RtiaCalTable[OptRtiaIndex+1]);
if(MagMean < mag*0.97f) /* @todo add threshold?? */
break;
OptRtiaIndex ++;
}
return OptRtiaIndex;
}
/**
* @brief Data pre-process
* @details Depending on the data type, do appropriate data pre-process before return back to controller
* @param pData: the buffer points to data read back from FIFO. Not needed for this application-RAMP
* @param pDataCount: The data count in pData buffer.
* @return return error code.
*/
static AD5940Err AppEDADataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t DataCount = *pDataCount;
*pDataCount = 0;
/* EDA results are DFT results */
for(uint32_t i=0; i<DataCount; i++)
{
pData[i] &= 0x3ffff; /* @todo option to check ECC */
if(pData[i]&(1<<17)) /* Bit17 is sign bit */
pData[i] |= 0xfffc0000; /* Data is 18bit in two's complement, bit17 is the sign bit */
}
if(AppEDACfg.EDAStateCurr == EDASTATE_VOLT)
{
uint32_t DftResCnt;
iImpCar_Type *pDftRes = (iImpCar_Type*)pData;
int32_t SumReal = 0, SumImage = 0;
/* Get average excitation voltage */
if(DataCount != AppEDACfg.VoltCalPoints*2)
return EDAERR_VOLTMEASURE;
DftResCnt = DataCount/2;
if(DftResCnt > 4)
{
DftResCnt -= 4;
pDftRes += 4; /* Discard the first 4 results */
}
for(uint32_t i=0;i<DftResCnt;i++)
{
SumReal += pDftRes[i].Real;
SumImage += pDftRes[i].Image;
////printf("Volt, %d, %d\n", pDftRes[i].Real, pDftRes[i].Image);
}
SumReal /= (int32_t)DftResCnt;
SumImage /= (int32_t)DftResCnt; /* Get average result */
SumImage = -SumImage; /* Fix sign of imaginary part of DFT result. */
AppEDACfg.ExcitVolt.Real = SumReal;
AppEDACfg.ExcitVolt.Image = SumImage;
//printf("Volt:%f,%f\n", AppEDACfg.ExcitVolt.Real, AppEDACfg.ExcitVolt.Image);
/* Done */
*pDataCount = 0; /* Don't return voltage result */
}
else if(AppEDACfg.EDAStateCurr == EDASTATE_CURR)/* The FIFO data is current result. We need to calculate impedance, Z=V/I */
{
iImpCar_Type * const pSrc = (iImpCar_Type*)pData;
fImpCar_Type * const pOut = (fImpCar_Type*)pData;
for(uint32_t i=0; i<DataCount/2; i++)
{
fImpCar_Type DftCurr;
fImpCar_Type res;
DftCurr.Real = (float)pSrc[i].Real;
DftCurr.Image = (float)pSrc[i].Image;
DftCurr.Image = -DftCurr.Image;
DftCurr.Real = -DftCurr.Real;
DftCurr.Image = -DftCurr.Image;
res = AD5940_ComplexDivFloat(&DftCurr, &AppEDACfg.RtiaCurrValue); /* I=Vrtia/Zrtia */
res = AD5940_ComplexDivFloat(&AppEDACfg.ExcitVolt, &res);
AppEDACfg.ImpSum = AD5940_ComplexAddFloat(&AppEDACfg.ImpSum ,&res);
AppEDACfg.ImpSumCount ++;
res = AD5940_ComplexSubFloat(&res, &AppEDACfg.ImpEDABase);
pOut[i] = res;
}
*pDataCount = DataCount/2; /* Impedance result */
/* Process RTIA autoscaling calculation */
if(AppEDACfg.RtiaAutoScaleEnable)
{
static uint32_t rtia_pre = (uint32_t)-1; /* Init to invalid value */
uint32_t rtia_index;
AppEDACfg.RtiaIndexCurr = AppEDACfg.RtiaIndexNext;
AppEDACfg.RtiaCurrValue = AppEDACfg.RtiaCalTable[AppEDACfg.RtiaIndexCurr];
rtia_index = EDARtiaAutoScaling(pOut,*pDataCount);
if(rtia_index != rtia_pre)
{
AppEDACfg.bChangeRtia = bTRUE;
rtia_pre = rtia_index;
AppEDACfg.RtiaIndexNext = rtia_index;
}
}
}
AppEDACfg.EDAStateCurr = AppEDACfg.EDAStateNext; /* We have to move forward the state at end of data processing */
return AD5940ERR_OK;
}
/**
* @brief The interrupt service routine for EDA.
* @param pBuff: The buffer provides by host, used to store data read back from FIFO.
* @param pCount: The available buffer size starts from pBuff.
* @return return error code.
*/
AD5940Err AppEDAISR(void *pBuff, uint32_t *pCount)
{
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
*pCount = 0;
if(AppEDACfg.EDAInited == bFALSE)
return AD5940ERR_APPERROR;
if(AD5940_WakeUp(20) > 20) /* Wakeup AFE by read register, read 20 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* Don't enter hibernate */
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);
AppEDARegModify(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); /* Don't enter hibernate */
/* Process data */
AppEDADataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return AD5940ERR_OK;
}
return AD5940ERR_WAKEUP;
}
/**
* @}
* @}
*/

View File

@ -0,0 +1,139 @@
/*!
*****************************************************************************
@file: ElectrodermalActivity.h
@author: Neo Xu
@brief: skin 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 _EDA_H_
#define _EDA_H_
#include "ad5940.h"
#include "stdio.h"
#include "string.h"
#include "math.h"
/* Do not modify following parameters */
#define LPTIAPA_PMOD 0 /* Power Mode of PA and LPTIA, Set to Half Power Mode is better for power consumption, 0: normal. 0x18: boost power. BITM_AFE_ULPTIACON0_HALFPWR: half power */
#define LPF_RF LPTIARF_20K /* Set RF resistor of Low Pass Filter */
#define LPF_TIME 10.0 /* Unit is ms. Low Pass Filter need time to settle. 10ms is OK for now */
#define LPTIASW_VOLT LPTIASW(5)|LPTIASW(6)|LPTIASW(7)|LPTIASW(8)|LPTIASW(9)|LPTIASW(13)
#define LPTIASW_CURR LPTIASW(2)|LPTIASW(5)|LPTIASW(10)|LPTIASW(13)
/**
* @brief The structure for sequencer patch.
*/
typedef struct
{
enum __PatchType
{
PATCHTYPE_VOLT = 0, /**< Generate patch for measuring voltage */
PATCHTYPE_CURR, /**< Generate patch for measuring current of body */
}Type;
uint32_t RtiaSel; /**< LPTIA RTIA selection */
const uint32_t *pSeqCmd; /**< The sequence to measure voltage and current is similar. The difference is stored in a command patch. */
uint32_t SeqLen; /**< Length of patch sequence */
uint32_t SRAMAddr; /**< Start address of the sequence command patch */
uint32_t Buffer[32]; /**< 32Byte should be enough for sequence generator */
const uint32_t BuffLen; /**< The buffer length of Buffer */
}SeqPatchInfo_Type;
/*
Note: this example will use SEQID_0 as measurement sequence, and use SEQID_1 as init sequence.
SEQID_3 is used for calibration if there is need.
*/
typedef struct
{
/* Common configurations for all kinds of Application. */
BoolFlag bParaChanged; /**< Indicate to generate sequence again. It's auto cleared by AppEDAInit */
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 */
BoolFlag bBioElecBoard; /**< Select between AD5941Sens1 board and BioElec board */
BoolFlag ReDoRtiaCal; /**< Set this flag to bTRUE when there is need to do calibration. */
float SysClkFreq; /**< The real frequency of system clock */
float LfoscClkFreq; /**< The clock frequency of Wakeup Timer in Hz. Typically it's 32kHz. Leave it here in case we calibrate clock in software method */
float AdcClkFreq; /**< The real frequency of ADC clock */
uint32_t FifoThresh; /**< FIFO threshold. Should be N*4 */
float EDAODR; /**< in Hz. ODR decides the period of WakeupTimer who will trigger sequencer periodically. DFT number and sample frequency decides the maxim ODR. */
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. */
uint32_t VoltCalPoints; /**< Use how many points to calculate average excitation voltage */
float RcalVal; /**< Rcal value in Ohm */
float SinFreq; /**< Frequency of excitation signal */
float SampleFreq; /**< Sample Frequency in Hz. Clock source is 32kHz.*/
float SinAmplitude; /**< Signal in amplitude in mV unit. Range: 0Vp to 1100mVp (0Vpp to 2.2Vpp) */
uint32_t DacUpdateRate; /**< DAC update rate is SystemCLock/Divider. The available value is 7 to 255. */
uint32_t LptiaRtiaSel; /**< Use internal RTIA, Select from LPTIARTIA_OPEN, LPTIARTIA_200R, ... , LPTIARTIA_512K */
uint32_t DftNum; /**< DFT number */
BoolFlag HanWinEn; /**< Enable Hanning window */
BoolFlag RtiaAutoScaleEnable; /**< Automatically change RTIA value according to measurement results. 0: Set RTIA with RTIA_SEL. 1: Automatically choose RTIA in software */
uint32_t RtiaAutoScaleMax; /**< Limit the maximum RTIA value that auto scale function can use. Select from LPTIARTIA_OPEN, LPTIARTIA_200R, ... , LPTIARTIA_512K */
uint32_t RtiaAutoScaleMin; /**< Limit the minimum RTIA value that auto scale function can use. Select from LPTIARTIA_OPEN, LPTIARTIA_200R, ... , LPTIARTIA_512K */
/* Private variables for internal usage */
fImpCar_Type RtiaCurrValue; /**< Calibrated Rtia value of current frequency */
fImpCar_Type RtiaCalTable[LPTIARTIA_512K+1]; /**< Calibrated Rtia Value table */
fImpCar_Type ImpEDABase; /**< Impedance of EDA base line */
fImpCar_Type ImpSum; /**< Sum of all measured results. Used to calculate base line of EDA */
uint32_t ImpSumCount; /**< Count of data added to 'ImpSum' */
uint32_t RtiaIndexCurr; /**< Index value 0 to 26 means Open, 200Ohm, to 512kOhm */
uint32_t RtiaIndexNext;
BoolFlag bChangeRtia; /**< Auto scaling method says we need to change RTIA */
SeqPatchInfo_Type SeqPatchInfo; /**< The sequence patch for different RTIA and both voltage/current measurement */
fImpCar_Type ExcitVolt; /**< Measured excitation voltage result */
BoolFlag bDataIsVolt; /**< Current DFT result is voltage */
BoolFlag bMeasVoltReq; /**< User says we need to measure voltage */
BoolFlag EDAInited; /**< If the program run firstly, generated sequence commands */
SEQInfo_Type InitSeqInfo;
SEQInfo_Type MeasureSeqInfo;
BoolFlag StopRequired; /**< After FIFO is ready, stop the measurement sequence */
BoolFlag bRunning; /**< status of if EDA is running. Useful when send STOP_SYNC to detect if it's actually stopped. */
uint32_t FifoDataCount; /**< Count how many times impedance have been measured */
enum __EDAState{
EDASTATE_INIT = 0, /**< Initializing */
EDASTATE_RTIACAL, /**< Internal RTIA resistor calibrating. */
EDASTATE_VOLT, /**< Measuring excitation voltage */
EDASTATE_CURR, /**< Measuring respond current */
}EDAStateCurr, EDAStateNext; /**< When interrupt happens, the state is EDACurrState. At the end of interrupt function, go to EDANextState */
/* End */
}AppEDACfg_Type;
/* Common application control message */
#define APPCTRL_START 0 /**< Start the measurement by starting Wakeup Timer */
#define APPCTRL_STOPNOW 1 /**< Stop immediately by stop Wakeup Timer*/
#define APPCTRL_STOPSYNC 2 /**< Stop the measurement when interrupt occurred */
#define APPCTRL_SHUTDOWN 3 /**< Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
#define APPCTRL_RUNNING 4 /**< Is application running? */
#define EDACTRL_MEASVOLT 100 /**< Measure Excitation voltage now */
#define EDACTRL_GETRTIAMAG 101 /**< Get the rtia magnitude for current measured data */
#define EDACTRL_RSTBASE 102 /**< Reset base line of EDA result. */
#define EDACTRL_SETBASE 103 /**< Set base line of EDA result */
#define EDACTRL_GETAVR 104 /**< Get average value of all measured impedance */
#define EDACTRL_STATUS 105 /**< Get if EDA is running. */
/* Error message */
#define EDAERR_ERROR AD5940ERR_APPERROR /**< General error */
#define EDAERR_VOLTMEASURE AD5940ERR_APPERROR-1 /**< Excitation voltage measurement error. Points not match */
AD5940Err AppEDAGetCfg(void *pCfg);
AD5940Err AppEDAInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppEDAISR(void *pBuff, uint32_t *pCount);
AD5940Err AppEDACtrl(int32_t EDACtrl, void *pPara);
#endif

585
examples/Impedance.c.txt Normal file
View File

@ -0,0 +1,585 @@
/*!
*****************************************************************************
@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 = 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_HP,
.HstiaRtiaSel = HSTIARTIA_5K,
.ExcitBufGain = EXCITBUFGAIN_2,
.HsDacGain = HSDACGAIN_1,
.HsDacUpdateRate = 7,
.DacVoltPP = 800.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);
/* 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_SEQGenInsert(SEQ_WAIT(WaitClks));
//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_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 */
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);
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);
}
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;
}

85
examples/Impedance.h.txt Normal file
View File

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

930
examples/RampTest.c.txt Normal file
View File

@ -0,0 +1,930 @@
/*!
*****************************************************************************
@file: RAMPTest.c
@author: Neo Xu
@brief: RAMP 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.
*****************************************************************************/
/** @addtogroup AD5940_System_Examples
* @{
* @defgroup Ramp_Test_Example
* @brief Using sequencer to generate ramp signal and control ADC to sample data.
* @details
* @note Need to update code when runs at S2 silicon.
* @todo update LPDAC switch settings for S2 and LPDAC 1LSB bug.
* @todo Calibrate ADC/PGA firstly to get accurate current. (Voltage/Rtia = Current)
* @note The method to calculate LPDAC ouput voltage
* - #define LSB_DAC12BIT (2.2V/4095)
* - #define LSB_DAC6BIT (2.2V/4095*64)
* - Volt_12bit = Code12Bit*LSB_DAC12BIT + 0.2V
* - Volt_6bit = Code6Bit*LSB_DAC6BIT + 0.2V
*
* # Ramp Signal Parameters definition
*
* @code
* (Vbias - Vzero):
* RampPeakVolt --> /\
* / \
* / \
* / \
* / \
* / \
* / \
* / \
* / \
* RampStartVolt --> / \
*
* Vzero: If there is no limitation on Vzero, Set VzeroStart to 2.2 and VzeroPeak to 0.4V
* Voltage VzeroStart --> ______ _____
* | |
* Voltage VzeroPeak --> |________|
*
*
* Vbias: Vbias is calculated from RampPeakVolt, RampStartVolt, VzeroStart and VzeroPeak.
* Voltage VbiasPeak --> /| /\ |\
* / | / \ | \
* / | / \ | \
* / |/ \| \
* Voltage VbiasStart --> / | | \
*
* RampState define: S0 | S1 | S2 |S3 | S4 |
* RampDuration define: | <--RampDuration--> |
* @endcode
*
* # The sequencer method to do Ramp test.
* The Ramp test need to update DAC data in real time to generate required waveform, and control ADC to start sample data. \n
* We used two kinds of sequence to realize it. One is to control DAC where SEQ0 and SEQ1 are used, another sequence SEQ2 controls ADC.
* ## Sequence Allocation
* SEQ3 is used to initialize AD5940.\n
* SEQ0/1 is used to generate voltage step.\n
* SEQ2 is used to startup ADC to sample one point.
*
* |SRAM allocation|||
* |------------|----------------|---------|
* |SequenceID | AddressRange | Usage |
* |SEQID_3 | 0x0000-0xzzzz | Initialization sequence|
* |SEQID_2 | 0xzzzz-0xyyyy | ADC control sequence, run this sequence will get one ADC result|
* |SEQID_0/1 | 0xyyyy-end | DAC update sequence. If size if not enough for all steps, use it like a Ping Pong buffer.|
* Where 0xzzzz equals to SEQ3 length, 0xyyyy equals to sum of SEQ2 and SEQ3 length.
* In one word, put SEQ2 commands right after SEQ3. Don't waste any SRAM resource.
* ##Sequencer Running Order
* The sequencer running order is set to firstly update DAC then start ADC. Repeat this process until all waveform generated.
* Below is explanation of sequencer running order.
* @code
* DAC voltage changes with sequencer, assume each step is 0.05V start from 0.2V
* 400mV-> _______
* 350mV-> _______/ \_______
* 300mV-> _______/ \_________
* 250mV-> _______/
* 200mV-> __/
* Update DAC: ↑ ↑ ↑ ↑ ↑ ↑ -No update
* SEQ0 SEQ1 SEQ0 SEQ1 SEQ0 SEQ1 SEQ0
* | / | / | / | / | / | / |
* SEQ2 SEQ2 SEQ2 SEQ2 SEQ2 SEQ2 |The final sequence is set to disable sequencer
* WuptTrigger ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
* Time Spend |t1| t2 |t1| t2 |t1| t2 |t1| t2 |t1| t2 |t1| t2 |t1| t2 |t1| t2
* |The following triggers are ignored because sequencer is disabled
* Wupt: Wakeup Timer
* @endcode
*
* The final sequence will disable sequencer thus disable the whole measurement. It could be SEQ0 or SEQ1. \n
* SEQ2 will always follow SEQ0/SEQ1 to turn on ADC to sample data. \n
* SEQ0/1 and SEQ2 is managed by wakeup timer. The time delay between SEQ0/1 and SEQ
* is set by user. Reason is that after updating DAC, signal needs some time to settle before sample it. \n
* In above figure, the time t1 is the delay set by user which controls where ADC starts sampling.
* (t1+t2)*StepNumber is the total time used by ramp. It's defined by @ref RampDuration.
*
* SEQ2 commands are fixed. Function is simply turn on ADC for a while and turn off it
* after required number of data ready. \n
* SEQ0/1 is always changing its start address to update DAC with different voltage. \n
* Check above figure we can see SEQ0/SEQ1 is repeatedly trigged by Wakeuptimer, if we don't change the start
* Address of SEQ0/SEQ1, they will always update DAC with same data, thus no waveform generated.
*
* Considering below SEQ0 command which is similar for SEQ1 on modifying SEQxINFO register.:
*
* **Sequencer Command Block 1**
* @code
* //Total sequence command length is **4**
* SEQ_WR(REG_AFE_LPDACDAT0, 0x1234); //update DAC with correct voltage
* SEQ_WAIT(10); //wait 10clocks to allow DAC update
* SEQ_WR(REG_AFE_SEQ1INFO, NextAddr|SeqLen); //The next sequence is SEQ1, set it to correct address where stores commands.
* SEQ_SLP(); //Put AFE to hibernate/sleep mode.
* @endcode
*
* It will update DAC with data 0x1234, then it wait 10 clocks to allow LPDAC update.
* The final command is to send AFE to sleep state.
* The third commands here is to allow modify sequence infomation by sequencer. Above piece of commands are running by SEQ0.
* It modify the start address of **SEQ1**. SEQ1 has same ability to update DAC data but with **different** data.
* By the time Wakeup Timer triggers SEQ1, it will update DAC with correct data.
*
* The last block of sequencer command is to disable sequencer.
*
* **Sequencer Command Block 2**
* @code
* SEQ_NOP();
* SEQ_NOP();
* SEQ_NOP();
* SEQ_STOP(); //Put AFE to hibernate/sleep mode.
* @endcode
*
* Total SRAM is 6kB in AD594x. In normal other application, we use 2kB for sequencer and 4kB for FIFO.
* Assume the ramp test require 128 steps, then the sequence length is 4*128 = 512, each command need 4Byte. So it costs 2kB SRAM.
* When ramp test requires hundres of voltage steps(ADC samples), 2kB SRAM is far from enough. We recommend to use 4kB for sequencer
* and 2kB for data FIFO.
* If ramp test require more steps, then we need to update SRAM with commands dynamically, use it as a ping-pong buffer.
*
* **Sequencer Command Block 3**
* @code
* SEQ_WR(REG_AFE_LPDACDAT0, 0x1234);
* SEQ_WAIT(10);
* SEQ_WR(REG_AFE_SEQ1INFO, NextAddr|SeqLen);
* SEQ_INT0(); //Generate custom interrupt 0 to inform MCU to update ping-pong buffer.
* @endcode
*
* @{
* **/
#include "ad5940.h"
#include <stdio.h>
#include "string.h"
#include "math.h"
#include "RampTest.h"
/**
* @brief The ramp application paramters.
* @details Do not modify following default parameters. Use the function in AD5940Main.c to change it.
*
* */
AppRAMPCfg_Type AppRAMPCfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.LFOSCClkFreq = 32000.0,
.SysClkFreq = 16000000.0,
.AdcClkFreq = 16000000.0,
.RcalVal = 10000.0,
.ADCRefVolt = 1820.0f, /* 1.8V or 1.82V? */
.bTestFinished = bFALSE,
/* Describe Ramp signal */
.RampStartVolt = -1000.0f, /* -1V */
.RampPeakVolt = +1000.0f, /* +1V */
.VzeroStart = 2200.0f, /* 2.2V */
.VzeroPeak = 400.0f, /* 0.4V */
.StepNumber = 866,
.RampDuration = 240 * 1000, /* 240s */
/* Receive path configuration */
.SampleDelay = 1.0f, /* 1ms */
.LPTIARtiaSel = LPTIARTIA_20K, /* Maximum current decides RTIA value */
.ExternalRtiaValue = 20000.0f, /* Optional external RTIA resistore value in Ohm. */
.AdcPgaGain = ADCPGA_1,
.ADCSinc3Osr = ADCSINC3OSR_2,
.FifoThresh = 4,
/* Priviate parameters */
.RAMPInited = bFALSE,
.StopRequired = bFALSE,
.RampState = RAMP_STATE0,
.bFirstDACSeq = bTRUE,
.bRampOneDir = bFALSE,
};
/**
* @todo add paramater check.
* SampleDelay will limited by wakeup timer, check WUPT register value calculation equation below for reference.
* SampleDelay > 1.0ms is acceptable.
* RampDuration/StepNumber > 2.0ms
* ...
* */
/**
* @brief This function is provided for upper controllers that want to change
* application parameters specially for user defined parameters.
* @param pCfg: The pointer used to store application configuration structure pointer.
* @return none.
*/
AD5940Err AppRAMPGetCfg(void *pCfg)
{
if(pCfg)
{
*(AppRAMPCfg_Type **)pCfg = &AppRAMPCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
/**
* @brief Control application like start, stop.
* @param Command: The command for this application, select from below paramters
* - APPCTRL_START: start the measurement. Note: the ramp test need firstly call function AppRAMPInit() every time before start it.
* - APPCTRL_STOPNOW: Stop the measurement immediately.
* - APPCTRL_STOPSYNC: Stop the measuremnt when current measured data is read back.
* - APPCTRL_SHUTDOWN: Stop the measurement immediately and put AFE to shut down mode(turn off LP loop and enter hibernate).
* @return none.
*/
AD5940Err AppRAMPCtrl(uint32_t Command, void *pPara)
{
switch (Command)
{
case APPCTRL_START:
{
WUPTCfg_Type wupt_cfg;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if(AppRAMPCfg.RAMPInited == bFALSE)
return AD5940ERR_APPERROR;
/**
* RAMP example is special, because the sequence is dynamically generated.
* Before 'START' ramp test, call AppRAMPInit firstly.
*/
if(AppRAMPCfg.RampState == RAMP_STOP)
return AD5940ERR_APPERROR;
/* Start it */
wupt_cfg.WuptEn = bTRUE;
wupt_cfg.WuptEndSeq = WUPTENDSEQ_D;
wupt_cfg.WuptOrder[0] = SEQID_0;
wupt_cfg.WuptOrder[1] = SEQID_2;
wupt_cfg.WuptOrder[2] = SEQID_1;
wupt_cfg.WuptOrder[3] = SEQID_2;
wupt_cfg.SeqxSleepTime[SEQID_2] = 4;
wupt_cfg.SeqxWakeupTime[SEQID_2] = (uint32_t)(AppRAMPCfg.LFOSCClkFreq * AppRAMPCfg.SampleDelay / 1000.0f) - 4 - 2;
wupt_cfg.SeqxSleepTime[SEQID_0] = 4;
wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(AppRAMPCfg.LFOSCClkFreq * (AppRAMPCfg.RampDuration / AppRAMPCfg.StepNumber - AppRAMPCfg.SampleDelay) / 1000.0f) - 4 - 2;
wupt_cfg.SeqxSleepTime[SEQID_1] = wupt_cfg.SeqxSleepTime[SEQID_0];
wupt_cfg.SeqxWakeupTime[SEQID_1] = wupt_cfg.SeqxWakeupTime[SEQID_0];
AD5940_WUPTCfg(&wupt_cfg);
break;
}
case APPCTRL_STOPNOW:
{
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Start Wupt right now */
AD5940_WUPTCtrl(bFALSE);
/* There is chance this operation will fail because sequencer could put AFE back
to hibernate mode just after waking up. Use STOPSYNC is better. */
AD5940_WUPTCtrl(bFALSE);
break;
}
case APPCTRL_STOPSYNC:
{
AppRAMPCfg.StopRequired = bTRUE;
break;
}
case APPCTRL_SHUTDOWN:
{
AppRAMPCtrl(APPCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
AD5940_ShutDownS();
}
break;
default:
break;
}
return AD5940ERR_OK;
}
/**
* @brief Generate initialization sequence and write the commands to SRAM.
* @return return error code.
*/
static AD5940Err AppRAMPSeqInitGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
LPLoopCfg_Type lploop_cfg;
DSPCfg_Type dsp_cfg;
/* 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*/
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
lploop_cfg.LpAmpCfg.LpAmpSel = LPAMP0;
lploop_cfg.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_BOOST3;
lploop_cfg.LpAmpCfg.LpPaPwrEn = bTRUE;
lploop_cfg.LpAmpCfg.LpTiaPwrEn = bTRUE;
lploop_cfg.LpAmpCfg.LpTiaRf = LPTIARF_20K;
lploop_cfg.LpAmpCfg.LpTiaRload = AppRAMPCfg.LPTIARloadSel;
lploop_cfg.LpAmpCfg.LpTiaRtia = AppRAMPCfg.LPTIARtiaSel;
if(AppRAMPCfg.LPTIARtiaSel == LPTIARTIA_OPEN) /* User want to use external RTIA */
lploop_cfg.LpAmpCfg.LpTiaSW = LPTIASW(2) | LPTIASW(4) | LPTIASW(5) | LPTIASW(9)/*|LPTIASW(10)*/; /* SW5/9 is closed to support external RTIA resistor */
else
lploop_cfg.LpAmpCfg.LpTiaSW = LPTIASW(2)|LPTIASW(4)|LPTIASW(5);
lploop_cfg.LpDacCfg.LpdacSel = LPDAC0;
lploop_cfg.LpDacCfg.DacData12Bit = 0x800;
lploop_cfg.LpDacCfg.DacData6Bit = 0;
lploop_cfg.LpDacCfg.DataRst = bFALSE;
lploop_cfg.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA/*|LPDACSW_VBIAS2PIN*/ | LPDACSW_VZERO2LPTIA/*|LPDACSW_VZERO2PIN*/;
lploop_cfg.LpDacCfg.LpDacRef = LPDACREF_2P5;
lploop_cfg.LpDacCfg.LpDacSrc = LPDACSRC_MMR;
lploop_cfg.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; /* Step Vbias. Use 12bit DAC ouput */
lploop_cfg.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT; /* Base is Vzero. Use 6 bit DAC ouput */
lploop_cfg.LpDacCfg.PowerEn = bTRUE;
AD5940_LPLoopCfgS(&lploop_cfg);
AD5940_StructInit(&dsp_cfg, sizeof(dsp_cfg));
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_LPTIA0_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_LPTIA0_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppRAMPCfg.AdcPgaGain;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppRAMPCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* ADC runs at 16MHz clock in this example, sample rate is 800kHz */
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE; /* We use data from SINC3 filter */
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = ADCSINC2OSR_1067; /* Don't care */
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_2; /* Don't care because it's disabled */
AD5940_DSPCfgS(&dsp_cfg);
/* 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 sequence generator here */
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
if(error == AD5940ERR_OK)
{
AD5940_StructInit(&AppRAMPCfg.InitSeqInfo, sizeof(AppRAMPCfg.InitSeqInfo));
if(SeqLen >= AppRAMPCfg.MaxSeqLen)
return AD5940ERR_SEQLEN;
AppRAMPCfg.InitSeqInfo.SeqId = SEQID_3;
AppRAMPCfg.InitSeqInfo.SeqRamAddr = AppRAMPCfg.SeqStartAddr;
AppRAMPCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppRAMPCfg.InitSeqInfo.SeqLen = SeqLen;
AppRAMPCfg.InitSeqInfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&AppRAMPCfg.InitSeqInfo);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
/**
* @brief Generate ADC control sequence and write the commands to SRAM.
* @return return error code.
*/
static AD5940Err AppRAMPSeqADCCtrlGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
ClksCalInfo_Type clks_cal;
clks_cal.DataCount = 1; /* Sample one point everytime */
clks_cal.DataType = DATATYPE_SINC3;
clks_cal.ADCSinc3Osr = AppRAMPCfg.ADCSinc3Osr;
clks_cal.ADCSinc2Osr = ADCSINC2OSR_1067; /* Don't care */
clks_cal.ADCAvgNum = ADCAVGNUM_2; /* Don't care */
clks_cal.RatioSys2AdcClk = AppRAMPCfg.SysClkFreq / AppRAMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin2);
AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16 * 250)); /* wait 250us for reference power up */
AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_AFECtrlS(AFECTRL_ADCPWR | AFECTRL_ADCCNV, bFALSE); /* Stop ADC */
AD5940_SEQGpioCtrlS(0);
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if(error == AD5940ERR_OK)
{
AD5940_StructInit(&AppRAMPCfg.ADCSeqInfo, sizeof(AppRAMPCfg.ADCSeqInfo));
if((SeqLen + AppRAMPCfg.InitSeqInfo.SeqLen) >= AppRAMPCfg.MaxSeqLen)
return AD5940ERR_SEQLEN;
AppRAMPCfg.ADCSeqInfo.SeqId = SEQID_2;
AppRAMPCfg.ADCSeqInfo.SeqRamAddr = AppRAMPCfg.InitSeqInfo.SeqRamAddr + AppRAMPCfg.InitSeqInfo.SeqLen ;
AppRAMPCfg.ADCSeqInfo.pSeqCmd = pSeqCmd;
AppRAMPCfg.ADCSeqInfo.SeqLen = SeqLen;
AppRAMPCfg.ADCSeqInfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&AppRAMPCfg.ADCSeqInfo);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
/**
* @brief Calculate DAC code step by step.
* @details The calculation is based on following variables.
* - RampStartVolt
* - RampPeakVolt
* - VzeroStart
* - VzeroPeak
* - StepNumber
* Below variables must be initialzed before call this function. It's done in function @ref AppRAMPInit
* - RampState
* - CurrStepPos
* - bDACCodeInc
* - CurrRampCode
* @return return error code.
*/
static AD5940Err RampDacRegUpdate(uint32_t *pDACData)
{
uint32_t VbiasCode, VzeroCode;
if (AppRAMPCfg.bRampOneDir)
{
switch(AppRAMPCfg.RampState)
{
case RAMP_STATE0: /* Begin of Ramp */
AppRAMPCfg.CurrVzeroCode = (uint32_t)((AppRAMPCfg.VzeroStart - 200.0f) / DAC6BITVOLT_1LSB);
AppRAMPCfg.RampState = RAMP_STATE1;
break;
case RAMP_STATE1:
if(AppRAMPCfg.CurrStepPos >= AppRAMPCfg.StepNumber / 2)
{
AppRAMPCfg.RampState = RAMP_STATE4; /* Enter State4 */
AppRAMPCfg.CurrVzeroCode = (uint32_t)((AppRAMPCfg.VzeroPeak - 200.0f) / DAC6BITVOLT_1LSB);
}
break;
case RAMP_STATE4:
if(AppRAMPCfg.CurrStepPos >= AppRAMPCfg.StepNumber)
AppRAMPCfg.RampState = RAMP_STOP; /* Enter Stop */
break;
case RAMP_STOP:
break;
}
}
else
{
switch(AppRAMPCfg.RampState)
{
case RAMP_STATE0: /* Begin of Ramp */
AppRAMPCfg.CurrVzeroCode = (uint32_t)((AppRAMPCfg.VzeroStart - 200.0f) / DAC6BITVOLT_1LSB);
AppRAMPCfg.RampState = RAMP_STATE1;
break;
case RAMP_STATE1:
if(AppRAMPCfg.CurrStepPos >= AppRAMPCfg.StepNumber / 4)
{
AppRAMPCfg.RampState = RAMP_STATE2; /* Enter State2 */
AppRAMPCfg.CurrVzeroCode = (uint32_t)((AppRAMPCfg.VzeroPeak - 200.0f) / DAC6BITVOLT_1LSB);
}
break;
case RAMP_STATE2:
if(AppRAMPCfg.CurrStepPos >= (AppRAMPCfg.StepNumber * 2) / 4)
{
AppRAMPCfg.RampState = RAMP_STATE3; /* Enter State3 */
AppRAMPCfg.bDACCodeInc = AppRAMPCfg.bDACCodeInc ? bFALSE : bTRUE;
}
break;
case RAMP_STATE3:
if(AppRAMPCfg.CurrStepPos >= (AppRAMPCfg.StepNumber * 3) / 4)
{
AppRAMPCfg.RampState = RAMP_STATE4; /* Enter State4 */
AppRAMPCfg.CurrVzeroCode = (uint32_t)((AppRAMPCfg.VzeroStart - 200.0f) / DAC6BITVOLT_1LSB);
}
break;
case RAMP_STATE4:
if(AppRAMPCfg.CurrStepPos >= AppRAMPCfg.StepNumber)
AppRAMPCfg.RampState = RAMP_STOP; /* Enter Stop */
break;
case RAMP_STOP:
break;
}
}
AppRAMPCfg.CurrStepPos ++;
if(AppRAMPCfg.bDACCodeInc)
AppRAMPCfg.CurrRampCode += AppRAMPCfg.DACCodePerStep;
else
AppRAMPCfg.CurrRampCode -= AppRAMPCfg.DACCodePerStep;
VzeroCode = AppRAMPCfg.CurrVzeroCode;
VbiasCode = (uint32_t)(VzeroCode * 64 + AppRAMPCfg.CurrRampCode);
if(VbiasCode < (VzeroCode * 64))
VbiasCode --;
/* Truncate */
if(VbiasCode > 4095) VbiasCode = 4095;
if(VzeroCode > 63) VzeroCode = 63;
*pDACData = (VzeroCode << 12) | VbiasCode;
return AD5940ERR_OK;
}
/* Geneate sequence(s) to update DAC step by step */
/* Note: this function doesn't need sequencer generator */
/**
* @brief Update DAC sequence in SRAM in real time.
* @details This function generates sequences to update DAC code step by step. It's also called in interrupt
* function when half commands in SRAM has been completed. We don't use sequence generator to save memory.
* Check more details from documentation of this example. @ref Ramp_Test_Example
* @return return error code
*
* */
static AD5940Err AppRAMPSeqDACCtrlGen(void)
{
#define SEQLEN_ONESTEP 4L /* How many sequence commands are needed to update LPDAC. */
#define CURRBLK_BLK0 0 /* Current block is BLOCK0 */
#define CURRBLK_BLK1 1 /* Current block is BLOCK1 */
AD5940Err error = AD5940ERR_OK;
uint32_t BlockStartSRAMAddr;
uint32_t DACData, SRAMAddr;
uint32_t i;
uint32_t StepsThisBlock;
BoolFlag bIsFinalBlk;
uint32_t SeqCmdBuff[SEQLEN_ONESTEP];
/* All below static variables are inited in below 'if' block. They are only used in this function */
static BoolFlag bCmdForSeq0 = bTRUE;
static uint32_t DACSeqBlk0Addr, DACSeqBlk1Addr;
static uint32_t StepsRemainning, StepsPerBlock, DACSeqCurrBlk;
/* Do some math calculations */
if(AppRAMPCfg.bFirstDACSeq == bTRUE)
{
/* Reset bIsFirstRun at end of function. */
int32_t DACSeqLenMax;
StepsRemainning = AppRAMPCfg.StepNumber;
DACSeqLenMax = (int32_t)AppRAMPCfg.MaxSeqLen - (int32_t)AppRAMPCfg.InitSeqInfo.SeqLen - (int32_t)AppRAMPCfg.ADCSeqInfo.SeqLen;
if(DACSeqLenMax < SEQLEN_ONESTEP * 4)
return AD5940ERR_SEQLEN; /* No enough sequencer SRAM available */
DACSeqLenMax -= SEQLEN_ONESTEP * 2; /* Reserve commands each block */
StepsPerBlock = DACSeqLenMax / SEQLEN_ONESTEP / 2;
DACSeqBlk0Addr = AppRAMPCfg.ADCSeqInfo.SeqRamAddr + AppRAMPCfg.ADCSeqInfo.SeqLen;
DACSeqBlk1Addr = DACSeqBlk0Addr + StepsPerBlock * SEQLEN_ONESTEP;
DACSeqCurrBlk = CURRBLK_BLK0;
/* Analog part */
if (AppRAMPCfg.bRampOneDir)
{
/* Ramping between RampStartVolt and RampPeakVolt in StepNumber steps */
AppRAMPCfg.DACCodePerStep = ((AppRAMPCfg.RampPeakVolt - AppRAMPCfg.RampStartVolt) / AppRAMPCfg.StepNumber)
/ DAC12BITVOLT_1LSB;
}
else
{
/* Ramping between RampStartVolt and RampPeakVolt in StepNumber/2 steps */
AppRAMPCfg.DACCodePerStep = ((AppRAMPCfg.RampPeakVolt - AppRAMPCfg.RampStartVolt) / AppRAMPCfg.StepNumber * 2)
/ DAC12BITVOLT_1LSB;
}
#if ALIGIN_VOLT2LSB
AppRAMPCfg.DACCodePerStep = (int32_t)AppRAMPCfg.DACCodePerStep;
#endif
if(AppRAMPCfg.DACCodePerStep > 0)
AppRAMPCfg.bDACCodeInc = bTRUE;
else
{
AppRAMPCfg.DACCodePerStep = -AppRAMPCfg.DACCodePerStep; /* Always positive */
AppRAMPCfg.bDACCodeInc = bFALSE;
}
AppRAMPCfg.CurrRampCode = AppRAMPCfg.RampStartVolt / DAC12BITVOLT_1LSB;
AppRAMPCfg.RampState = RAMP_STATE0; /* Init state to STATE0 */
AppRAMPCfg.CurrStepPos = 0;
bCmdForSeq0 = bTRUE; /* Start with SEQ0 */
}
if(StepsRemainning == 0) return AD5940ERR_OK; /* Done. */
bIsFinalBlk = StepsRemainning <= StepsPerBlock ? bTRUE : bFALSE;
if(bIsFinalBlk)
StepsThisBlock = StepsRemainning;
else
StepsThisBlock = StepsPerBlock;
StepsRemainning -= StepsThisBlock;
BlockStartSRAMAddr = (DACSeqCurrBlk == CURRBLK_BLK0) ? \
DACSeqBlk0Addr : DACSeqBlk1Addr;
SRAMAddr = BlockStartSRAMAddr;
for(i = 0; i < StepsThisBlock - 1; i++)
{
uint32_t CurrAddr = SRAMAddr;
SRAMAddr += SEQLEN_ONESTEP; /* Jump to next sequence */
RampDacRegUpdate(&DACData);
SeqCmdBuff[0] = SEQ_WR(REG_AFE_LPDACDAT0, DACData);
SeqCmdBuff[1] = SEQ_WAIT(10); /* !!!NOTE LPDAC need 10 clocks to update data. Before send AFE to sleep state, wait 10 extra clocks */
SeqCmdBuff[2] = SEQ_WR(bCmdForSeq0 ? REG_AFE_SEQ1INFO : REG_AFE_SEQ0INFO, \
(SRAMAddr << BITP_AFE_SEQ1INFO_ADDR) | (SEQLEN_ONESTEP << BITP_AFE_SEQ1INFO_LEN));
SeqCmdBuff[3] = SEQ_SLP();
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
bCmdForSeq0 = bCmdForSeq0 ? bFALSE : bTRUE;
}
/* Add final DAC update */
if(bIsFinalBlk)/* This is the final block */
{
uint32_t CurrAddr = SRAMAddr;
SRAMAddr += SEQLEN_ONESTEP; /* Jump to next sequence */
/* After update LPDAC with final data, we let sequencer to run 'final final' command, to disable sequencer. */
RampDacRegUpdate(&DACData);
SeqCmdBuff[0] = SEQ_WR(REG_AFE_LPDACDAT0, DACData);
SeqCmdBuff[1] = SEQ_WAIT(10); /* !!!NOTE LPDAC need 10 clocks to update data. Before send AFE to sleep state, wait 10 extra clocks */
SeqCmdBuff[2] = SEQ_WR(bCmdForSeq0 ? REG_AFE_SEQ1INFO : REG_AFE_SEQ0INFO, \
(SRAMAddr << BITP_AFE_SEQ1INFO_ADDR) | (SEQLEN_ONESTEP << BITP_AFE_SEQ1INFO_LEN));
SeqCmdBuff[3] = SEQ_SLP();
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
CurrAddr += SEQLEN_ONESTEP;
/* The final final command is to disable sequencer. */
SeqCmdBuff[0] = SEQ_NOP(); /* Do nothing */
SeqCmdBuff[1] = SEQ_NOP();
SeqCmdBuff[2] = SEQ_NOP();
SeqCmdBuff[3] = SEQ_STOP(); /* Stop sequencer. */
/* Disable sequencer, END of sequencer interrupt is generated. */
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
}
else /* This is not the final block */
{
/* Jump to next block. */
uint32_t CurrAddr = SRAMAddr;
SRAMAddr = (DACSeqCurrBlk == CURRBLK_BLK0) ? \
DACSeqBlk1Addr : DACSeqBlk0Addr;
RampDacRegUpdate(&DACData);
SeqCmdBuff[0] = SEQ_WR(REG_AFE_LPDACDAT0, DACData);
SeqCmdBuff[1] = SEQ_WAIT(10);
SeqCmdBuff[2] = SEQ_WR(bCmdForSeq0 ? REG_AFE_SEQ1INFO : REG_AFE_SEQ0INFO,
(SRAMAddr << BITP_AFE_SEQ1INFO_ADDR) | (SEQLEN_ONESTEP << BITP_AFE_SEQ1INFO_LEN));
SeqCmdBuff[3] = SEQ_INT0(); /* Generate Custom interrupt 0. */
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
bCmdForSeq0 = bCmdForSeq0 ? bFALSE : bTRUE;
}
DACSeqCurrBlk = (DACSeqCurrBlk == CURRBLK_BLK0) ? \
CURRBLK_BLK1 : CURRBLK_BLK0; /* Switch between Block0 and block1 */
if(AppRAMPCfg.bFirstDACSeq)
{
AppRAMPCfg.bFirstDACSeq = bFALSE;
if(bIsFinalBlk == bFALSE)
{
/* Otherwise there is no need to init block1 sequence */
error = AppRAMPSeqDACCtrlGen();
if(error != AD5940ERR_OK)
return error;
}
/* This is the first DAC sequence. */
AppRAMPCfg.DACSeqInfo.SeqId = SEQID_0;
AppRAMPCfg.DACSeqInfo.SeqLen = SEQLEN_ONESTEP;
AppRAMPCfg.DACSeqInfo.SeqRamAddr = BlockStartSRAMAddr;
AppRAMPCfg.DACSeqInfo.WriteSRAM = bFALSE; /* No need to write to SRAM. We already write them above. */
AD5940_SEQInfoCfg(&AppRAMPCfg.DACSeqInfo);
}
return AD5940ERR_OK;
}
/**
* @brief Calibrate LPTIA internal RTIA resistor(s).
* @details This function will do calibration using parameters stored in @ref AppEDACfg structure.
* @return return error code.
*/
static AD5940Err AppRAMPRtiaCal(void)
{
fImpPol_Type RtiaCalValue; /* Calibration result */
LPRTIACal_Type lprtia_cal;
AD5940_StructInit(&lprtia_cal, sizeof(lprtia_cal));
lprtia_cal.LpAmpSel = LPAMP0;
lprtia_cal.bPolarResult = bTRUE; /* Magnitude + Phase */
lprtia_cal.AdcClkFreq = AppRAMPCfg.AdcClkFreq;
lprtia_cal.SysClkFreq = AppRAMPCfg.SysClkFreq;
lprtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
lprtia_cal.ADCSinc2Osr = ADCSINC2OSR_22; /* Use SINC2 data as DFT data source */
lprtia_cal.DftCfg.DftNum = DFTNUM_2048; /* Maximum DFT number */
lprtia_cal.DftCfg.DftSrc = DFTSRC_SINC2NOTCH;
lprtia_cal.DftCfg.HanWinEn = bTRUE;
lprtia_cal.fFreq = AppRAMPCfg.AdcClkFreq / 4 / 22 / 2048 * 3; /* Sample 3 period of signal, 13.317Hz here. Do not use DC method, because it needs ADC/PGA calibrated firstly(but it's faster) */
lprtia_cal.fRcal = AppRAMPCfg.RcalVal;
lprtia_cal.LpTiaRtia = AppRAMPCfg.LPTIARtiaSel;
lprtia_cal.LpAmpPwrMod = LPAMPPWR_NORM;
lprtia_cal.bWithCtia = bFALSE;
AD5940_LPRtiaCal(&lprtia_cal, &RtiaCalValue);
AppRAMPCfg.RtiaValue = RtiaCalValue;
//printf("Rtia,%f,%f\n", RtiaCalValue.Magnitude, RtiaCalValue.Phase);
return AD5940ERR_OK;
}
/**
* @brief Initialize the ramp test. Call this functions every time before start ramp test.
* @param pBuffer: the buffer for sequencer generator. Only need to provide it for the first time.
* @param BufferSize: The buffer size start from pBuffer.
* @return return error code.
*/
AD5940Err AppRAMPInit(uint32_t *pBuffer, uint32_t BufferSize)
{
AD5940Err error = AD5940ERR_OK;
FIFOCfg_Type fifo_cfg;
SEQCfg_Type seq_cfg;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Configure sequencer and stop it */
seq_cfg.SeqMemSize = SEQMEMSIZE_4KB;
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Start sequence generator */
/* Initialize sequencer generator */
if((AppRAMPCfg.RAMPInited == bFALSE) || \
(AppRAMPCfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
if(AppRAMPCfg.LPTIARtiaSel == LPTIARTIA_OPEN) /* Internal RTIA is opened. User wants to use external RTIA resistor */
{
AppRAMPCfg.RtiaValue.Magnitude = AppRAMPCfg.ExternalRtiaValue;
AppRAMPCfg.RtiaValue.Phase = 0;
}
else
AppRAMPRtiaCal();
AppRAMPCfg.RAMPInited = bFALSE;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate sequence and write them to SRAM start from address AppRAMPCfg.SeqStartAddr */
error = AppRAMPSeqInitGen(); /* Application initialization sequence */
if(error != AD5940ERR_OK) return error;
error = AppRAMPSeqADCCtrlGen(); /* ADC control sequence */
if(error != AD5940ERR_OK) return error;
AppRAMPCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Reconfigure FIFO, The Rtia calibration function may generate data that stored to FIFO */
AD5940_FIFOCtrlS(FIFOSRC_SINC3, bFALSE); /* Disable FIFO firstly */
fifo_cfg.FIFOEn = bTRUE;
fifo_cfg.FIFOSrc = FIFOSRC_SINC3;
fifo_cfg.FIFOThresh = AppRAMPCfg.FifoThresh; /* Change FIFO paramters */
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_2KB;
AD5940_FIFOCfg(&fifo_cfg);
/* Clear all interrupts */
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Generate DAC sequence */
AppRAMPCfg.bFirstDACSeq = bTRUE;
error = AppRAMPSeqDACCtrlGen();
if(error != AD5940ERR_OK) return error;
/* Configure sequence info. */
AppRAMPCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppRAMPCfg.InitSeqInfo);
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer */
AD5940_SEQMmrTrig(AppRAMPCfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ);
AppRAMPCfg.ADCSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppRAMPCfg.ADCSeqInfo);
AppRAMPCfg.DACSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppRAMPCfg.DACSeqInfo);
AD5940_SEQCtrlS(bFALSE);
AD5940_WriteReg(REG_AFE_SEQCNT, 0);
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ); /* Set to low power mode */
AppRAMPCfg.RAMPInited = bTRUE; /* RAMP application has been initialized. */
return AD5940ERR_OK;
}
/**
* @brief This function is called in ISR when AFE has been wakeup and we can access registers.
* @param pData: the buffer points to data read back from FIFO. Not needed for this application-RAMP
* @param pDataCount: The data count in pData buffer.
* @return return error code.
*/
static int32_t AppRAMPRegModify(int32_t *const pData, uint32_t *pDataCount)
{
if(AppRAMPCfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
return AD5940ERR_OK;
}
/**
* @brief Depending on the data type, do appropriate data pre-process before return back to controller
* @param pData: the buffer points to data read back from FIFO. Not needed for this application-RAMP
* @param pDataCount: The data count in pData buffer.
* @return return error code.
*/
static int32_t AppRAMPDataProcess(int32_t *const pData, uint32_t *pDataCount)
{
uint32_t i, datacount;
datacount = *pDataCount;
float *pOut = (float *)pData;
float temp;
for(i = 0; i < datacount; i++)
{
pData[i] &= 0xffff;
temp = -AD5940_ADCCode2Volt(pData[i], AppRAMPCfg.AdcPgaGain, AppRAMPCfg.ADCRefVolt);
pOut[i] = temp / AppRAMPCfg.RtiaValue.Magnitude * 1e3f; /* Result unit is uA. */
}
return 0;
}
/**
* @brief The interrupt service routine for RAMP test.
* @param pBuff: The buffer provides by host, used to store data read back from FIFO.
* @param pCount: The available buffer size starts from pBuff.
* @return return error code.
*/
AD5940Err AppRAMPISR(void *pBuff, uint32_t *pCount)
{
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
uint32_t IntFlag;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK);
*pCount = 0;
IntFlag = AD5940_INTCGetFlag(AFEINTC_0);
if(IntFlag & AFEINTSRC_CUSTOMINT0) /* High priority. */
{
AD5940Err error;
AD5940_INTCClrFlag(AFEINTSRC_CUSTOMINT0);
error = AppRAMPSeqDACCtrlGen();
if(error != AD5940ERR_OK) return error;
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
//AD5940_EnterSleepS(); /* If there is need to do AFE re-configure, do it here when AFE is in active state */
}
if(IntFlag & AFEINTSRC_DATAFIFOTHRESH)
{
FifoCnt = AD5940_FIFOGetCnt();
if(FifoCnt > BuffCount)
{
///@todo buffer is limited.
}
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppRAMPRegModify(pBuff, &FifoCnt);
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
//AD5940_EnterSleepS();
/* Process data */
AppRAMPDataProcess((int32_t *)pBuff, &FifoCnt);
*pCount = FifoCnt;
return 0;
}
if(IntFlag & AFEINTSRC_ENDSEQ)
{
FifoCnt = AD5940_FIFOGetCnt();
AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ);
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
/* Process data */
AppRAMPDataProcess((int32_t *)pBuff, &FifoCnt);
*pCount = FifoCnt;
AppRAMPCtrl(APPCTRL_STOPNOW, 0); /* Stop the Wakeup Timer. */
/* Reset variables so measurement can be restarted*/
AppRAMPCfg.bTestFinished = bTRUE;
AppRAMPCfg.RampState = RAMP_STATE0;
AppRAMPCfg.bFirstDACSeq = bTRUE;
AppRAMPCfg.bDACCodeInc = bTRUE;
AppRAMPSeqDACCtrlGen();
}
return 0;
}
/**
* @}
* @}
*/

88
examples/RampTest.h.txt Normal file
View File

@ -0,0 +1,88 @@
/*!
*****************************************************************************
@file: RampTest.h
@author: Neo Xu
@brief: Ramp Test 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 _RAMPTEST_H_
#define _RAMPTEST_H_
#include "ad5940.h"
#include <stdio.h>
#include "string.h"
#include "math.h"
/* Do not modify following parameters */
#define ALIGIN_VOLT2LSB 0 /* Set it to 1 to align each voltage step to 1LSB of DAC. 0: step code is fractional. */
#define DAC12BITVOLT_1LSB (2200.0f/4095) //mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) //mV
/**
* The Ramp application related paramter structure
*/
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; /**< Not used for Ramp.Calibration sequence start address in SRAM of AD5940 */
uint32_t MaxSeqLenCal; /**< Not used for Ramp. */
/* Application related parameters */
float LFOSCClkFreq; /**< 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 */
float ADCRefVolt; /**< The real ADC voltage in mV. */
BoolFlag bTestFinished; /**< Variable to indicate ramt test has finished >*/
/* Describe Ramp signal */
float RampStartVolt; /**< The start voltage of ramp signal in mV */
float RampPeakVolt; /**< The maximum or minimum voltage of ramp in mV */
float VzeroStart; /**< The start voltage of Vzero in mV. Set it to 2400mV by default */
float VzeroPeak; /**< The peak voltage of Vzero in mV. Set it to 200mV by default */
uint32_t StepNumber; /**< Total number of steps. Limited to 4095. */
uint32_t RampDuration; /**< Ramp signal duration(total time) in ms */
/* Receive path configuration */
float SampleDelay; /**< The time delay between update DAC and start ADC */
uint32_t LPTIARtiaSel; /**< Select RTIA */
uint32_t LPTIARloadSel; /**< Select Rload */
float ExternalRtiaValue; /**< The optional external RTIA value in Ohm. Disconnect internal RTIA to use external RTIA. When using internal RTIA, this value is ignored. */
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; /**< We use data from SINC3 filter. */
/* Digital related */
uint32_t FifoThresh; /**< FIFO Threshold value */
/* Private variables for internal usage */
BoolFlag RAMPInited; /**< If the program run firstly, generated initialization sequence commands */
fImpPol_Type RtiaValue; /**< Calibrated Rtia value */
SEQInfo_Type InitSeqInfo;
SEQInfo_Type ADCSeqInfo;
BoolFlag bFirstDACSeq; /**< Init DAC sequence */
SEQInfo_Type DACSeqInfo; /**< The first DAC update sequence info */
uint32_t CurrStepPos; /**< Current position */
float DACCodePerStep; /**< */
float CurrRampCode; /**< */
uint32_t CurrVzeroCode;
BoolFlag bDACCodeInc; /**< Increase DAC code. */
BoolFlag StopRequired; /**< After FIFO is ready, stop the measurement sequence */
enum _RampState{RAMP_STATE0 = 0, RAMP_STATE1, RAMP_STATE2, RAMP_STATE3, RAMP_STATE4, RAMP_STOP} RampState;
BoolFlag bRampOneDir; /**< Ramp in a single direction, no return to start */
}AppRAMPCfg_Type;
#define APPCTRL_START 0
#define APPCTRL_STOPNOW 1
#define APPCTRL_STOPSYNC 2
#define APPCTRL_SHUTDOWN 3 /**< Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
AD5940Err AppRAMPInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppRAMPGetCfg(void *pCfg);
AD5940Err AppRAMPISR(void *pBuff, uint32_t *pCount);
AD5940Err AppRAMPCtrl(uint32_t Command, void *pPara);
#endif

View File

@ -0,0 +1,941 @@
/*!
*****************************************************************************
@file: SWVTest.c
@author: $Author: mlambe $
@brief: Square Wave Voltammetry measurement sequences.
@date: Updated 15th May 2021
-----------------------------------------------------------------------------
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_System_Examples
* @{
* @defgroup Ramp_Test_Example
* @brief Using sequencer to generate ramp signal and control ADC to sample data.
* @details
* @note Need to update code when runs at S2 silicon.
* @todo update LPDAC switch settings for S2 and LPDAC 1LSB bug.
* @todo Calibrate ADC/PGA firstly to get accurate current. (Voltage/Rtia = Current)
* @note The method to calculate LPDAC ouput voltage
* - #define LSB_DAC12BIT (2.2V/4095)
* - #define LSB_DAC6BIT (2.2V/4095*64)
* - Volt_12bit = Code12Bit*LSB_DAC12BIT + 0.2V
* - Volt_6bit = Code6Bit*LSB_DAC6BIT + 0.2V
*
* # Ramp Signal Parameters definition
*
* @code
* (Vbias - Vzero):
* RampPeakVolt --> /
* /
* /
* /
* /
* /
* /
* /
* /
* RampStartVolt --> /
*
* Vzero: If there is no limitation on Vzero, Set VzeroStart to 2.2 and VzeroPeak to 0.4V
* Voltage VzeroStart --> ______
* |
* Voltage VzeroPeak --> |_____
*
*
* Vbias: Vbias is calculated from RampPeakVolt, RampStartVolt, VzeroStart and VzeroPeak.
* Voltage VbiasPeak --> /| /
* / | /
* / | /
* / | /
* Voltage VbiasStart --> / |/
*
* RampState define: S0 | S1 | S2 |
* @endcode
*
* # The sequencer method to do Ramp test.
* The Ramp test need to update DAC data in real time to generate required waveform, and control ADC to start sample data. \n
* We used two kinds of sequence to realize it. One is to control DAC where SEQ0 and SEQ1 are used, another sequence SEQ2 controls ADC.
* ## Sequence Allocation
* SEQ3 is used to initialize AD5940.\n
* SEQ0/1 is used to generate voltage step.\n
* SEQ2 is used to startup ADC to sample one point.
*
* |SRAM allocation|||
* |------------|----------------|---------|
* |SequenceID | AddressRange | Usage |
* |SEQID_3 | 0x0000-0xzzzz | Initialization sequence|
* |SEQID_2 | 0xzzzz-0xyyyy | ADC control sequence, run this sequence will get one ADC result|
* |SEQID_0/1 | 0xyyyy-end | DAC update sequence. If size if not enough for all steps, use it like a FIFO.|
* Where 0xzzzz equals to SEQ3 length, 0xyyyy equals to sum of SEQ2 and SEQ3 length.
* In one word, put SEQ2 commands right after SEQ3. Don't waste any SRAM resource.
* ##Sequencer Running Order
* The sequencer running order is set to firstly update DAC then start ADC. Repeat this process until all waveform generated.
* Below is explanation of sequencer running order.
* @code
* DAC voltage changes with sequencer, assume each step is 0.05V start from 0.2V
* 400mV-> _______
* 350mV-> _____ |
* 300mV-> _____ | |____|
* 250mV-> | |____|
* 200mV-> __|
* Update DAC: ? ? ? ? ? ? -No update
* SEQ0 SEQ1 SEQ0 SEQ1 SEQ0 SEQ1 SEQ0
* | / | / | / | / | / | / |
* SEQ2 SEQ2 SEQ2 SEQ2 SEQ2 SEQ2 |The final sequence is set to disable sequencer
* WuptTrigger ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
* Time Spend |t1| t2 |t1| t2 |t1| t2 |t1| t2 |t1| t2 |t1| t2 |t1| t2 |t1| t2
* |The following triggers are ignored because sequencer is disabled
* Wupt: Wakeup Timer
* @endcode
*
* The final sequence will disable sequencer thus disable the whole measurement. It could be SEQ0 or SEQ1. \n
* SEQ2 will always follow SEQ0/SEQ1 to turn on ADC to sample data. \n
* SEQ0/1 and SEQ2 is managed by wakeup timer. The time delay between SEQ0/1 and SEQ
* is set by user. Reason is that after updating DAC, signal needs some time to settle before sample it. \n
* In above figure, the time t1 is the delay set by user which controls where ADC starts sampling.
*
* SEQ2 commands are fixed. Function is simply turn on ADC for a while and turn off it
* after required number of data ready. \n
* SEQ0/1 is always changing its start address to update DAC with different voltage. \n
* Check above figure we can see SEQ0/SEQ1 is repeatedly trigged by Wakeuptimer, if we don't change the start
* Address of SEQ0/SEQ1, they will always update DAC with same data, thus no waveform generated.
*
* Considering below SEQ0 command which is similar for SEQ1 on modifying SEQxINFO register.:
*
* **Sequencer Command Block 1**
* @code
* //Total sequence command length is **4**
* SEQ_WR(REG_AFE_LPDACDAT0, 0x1234); //update DAC with correct voltage
* SEQ_WAIT(10); //wait 10clocks to allow DAC update
* SEQ_WR(REG_AFE_SEQ1INFO, NextAddr|SeqLen); //The next sequence is SEQ1, set it to correct address where stores commands.
* SEQ_SLP(); //Put AFE to hibernate/sleep mode.
* @endcode
*
* It will update DAC with data 0x1234, then it wait 10 clocks to allow LPDAC update.
* The final command is to send AFE to sleep state.
* The third commands here is to allow modify sequence infomation by sequencer. Above piece of commands are running by SEQ0.
* It modify the start address of **SEQ1**. SEQ1 has same ability to update DAC data but with **different** data.
* By the time Wakeup Timer triggers SEQ1, it will update DAC with correct data.
*
* The last block of sequencer command is to disable sequencer.
*
* **Sequencer Command Block 2**
* @code
* SEQ_NOP();
* SEQ_NOP();
* SEQ_NOP();
* SEQ_STOP(); //Put AFE to hibernate/sleep mode.
* @endcode
*
* Total SRAM is 6kB in AD594x. In normal other application, we use 2kB for sequencer and 4kB for FIFO.
* Assume the ramp test require 128 steps, then the sequence length is 4*128 = 512, each command need 4Byte. So it costs 2kB SRAM.
* When ramp test requires hundres of voltage steps(ADC samples), 2kB SRAM is far from enough. We recommend to use 4kB for sequencer
* and 2kB for data FIFO.
* If ramp test require more steps, then we need to update SRAM with commands dynamically, use it as a ping-pong buffer.
*
* **Sequencer Command Block 3**
* @code
* SEQ_WR(REG_AFE_LPDACDAT0, 0x1234);
* SEQ_WAIT(10);
* SEQ_WR(REG_AFE_SEQ1INFO, NextAddr|SeqLen);
* SEQ_INT0(); //Generate custom interrupt 0 to inform MCU to update ping-pong buffer.
* @endcode
*
* @{
* **/
#include "ad5940.h"
#include <stdio.h>
#include "string.h"
#include "math.h"
#include "SqrWaveVoltammetry.h"
/**
* @brief The ramp application paramters.
* @details Do not modify following default parameters. Use the function in AD5940Main.c to change it.
*
* */
static AppSWVCfg_Type AppSWVCfg =
{
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.LFOSCClkFreq = 32000.0,
.SysClkFreq = 16000000.0,
.AdcClkFreq = 16000000.0,
.RcalVal = 10000.0,
.ADCRefVolt = 1820.0f, /* 1.8V or 1.82V? */
/* Describe Ramp signal */
.RampStartVolt = -1000.0f, /* -1V */
.RampPeakVolt = +1000.0f, /* +1V */
.VzeroStart = 2200.0f, /* 2.2V */
.VzeroPeak = 400.0f, /* 0.4V */
.StepNumber = 866,
/* Receive path configuration */
.SampleDelay = 1.0f, /* 1ms */
.LPTIARtiaSel = LPTIARTIA_20K, /* Maximum current decides RTIA value */
.ExternalRtiaValue = 20000.0f, /* Optional external RTIA resistore value in Ohm. */
.AdcPgaGain = ADCPGA_1,
.ADCSinc3Osr = ADCSINC3OSR_4,
.FifoThresh = 4,
/* Priviate parameters */
.SWVInited = bFALSE,
.StopRequired = bFALSE,
.RampState = SWV_STATE0,
.bFirstDACSeq = bTRUE,
.bSqrWaveHiLevel = bFALSE,
/* Configure Square wave */
.Frequency = 25, /* Frequency in Hz */
.SqrWvAmplitude = 25, /* Square wave amplitude in mV */
.SqrWvRampIncrement = 5, /* Ramp increment in mV*/
};
/**
* @todo add paramater check.
* SampleDelay will limited by wakeup timer, check WUPT register value calculation equation below for reference.
* SampleDelay > 1.0ms is acceptable.
* ...
* */
/**
* @brief This function is provided for upper controllers that want to change
* application parameters specially for user defined parameters.
* @param pCfg: The pointer used to store application configuration structure pointer.
* @return none.
*/
AD5940Err AppSWVGetCfg(void *pCfg)
{
if(pCfg)
{
*(AppSWVCfg_Type**)pCfg = &AppSWVCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
/**
* @brief Control application like start, stop.
* @param Command: The command for this application, select from below paramters
* - APPCTRL_START: start the measurement. Note: the ramp test need firstly call function AppSWVInit() every time before start it.
* - APPCTRL_STOPNOW: Stop the measurement immediately.
* - APPCTRL_STOPSYNC: Stop the measuremnt when current measured data is read back.
* - APPCTRL_SHUTDOWN: Stop the measurement immediately and put AFE to shut down mode(turn off LP loop and enter hibernate).
* @return none.
*/
AD5940Err AppSWVCtrl(uint32_t Command, void *pPara)
{
switch (Command)
{
case APPCTRL_START:
{
WUPTCfg_Type wupt_cfg;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if(AppSWVCfg.SWVInited == bFALSE)
return AD5940ERR_APPERROR;
/**
* SWV example is special, because the sequence is dynamically generated.
* Before 'START' ramp test, call AppSWVInit firstly.
*/
if(AppSWVCfg.RampState == SWV_STOP)
return AD5940ERR_APPERROR;
/* Start it */
wupt_cfg.WuptEn = bTRUE;
wupt_cfg.WuptEndSeq = WUPTENDSEQ_D;
wupt_cfg.WuptOrder[0] = SEQID_0;
wupt_cfg.WuptOrder[1] = SEQID_2;
wupt_cfg.WuptOrder[2] = SEQID_1;
wupt_cfg.WuptOrder[3] = SEQID_2;
wupt_cfg.SeqxSleepTime[SEQID_2] = 1;
wupt_cfg.SeqxWakeupTime[SEQID_2] = (uint32_t)(AppSWVCfg.LFOSCClkFreq*AppSWVCfg.SampleDelay/1000.0f) - 1;
wupt_cfg.SeqxSleepTime[SEQID_0] = 1;
wupt_cfg.SeqxWakeupTime[SEQID_0] = (uint32_t)(AppSWVCfg.LFOSCClkFreq*((1/AppSWVCfg.Frequency*500) - AppSWVCfg.SampleDelay)/1000.0f) - 4;
wupt_cfg.SeqxSleepTime[SEQID_1] = wupt_cfg.SeqxSleepTime[SEQID_0];
wupt_cfg.SeqxWakeupTime[SEQID_1] = wupt_cfg.SeqxWakeupTime[SEQID_0];
AD5940_WUPTCfg(&wupt_cfg);
break;
}
case APPCTRL_STOPNOW:
{
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Start Wupt right now */
AD5940_WUPTCtrl(bFALSE);
/* There is chance this operation will fail because sequencer could put AFE back
to hibernate mode just after waking up. Use STOPSYNC is better. */
AD5940_WUPTCtrl(bFALSE);
break;
}
case APPCTRL_STOPSYNC:
{
AppSWVCfg.StopRequired = bTRUE;
break;
}
case APPCTRL_SHUTDOWN:
{
AppSWVCtrl(APPCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
AD5940_ShutDownS();
}
break;
default:
break;
}
return AD5940ERR_OK;
}
/**
* @brief Generate initialization sequence and write the commands to SRAM.
* @return return error code.
*/
static AD5940Err AppSWVSeqInitGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
LPLoopCfg_Type lploop_cfg;
DSPCfg_Type dsp_cfg;
/* 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*/
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
lploop_cfg.LpAmpCfg.LpAmpSel = LPAMP0;
lploop_cfg.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_BOOST3;
lploop_cfg.LpAmpCfg.LpPaPwrEn = bTRUE;
lploop_cfg.LpAmpCfg.LpTiaPwrEn = bTRUE;
lploop_cfg.LpAmpCfg.LpTiaRf = LPTIARF_20K;
lploop_cfg.LpAmpCfg.LpTiaRload = LPTIARLOAD_SHORT;
lploop_cfg.LpAmpCfg.LpTiaRtia = AppSWVCfg.LPTIARtiaSel;
if(AppSWVCfg.LPTIARtiaSel == LPTIARTIA_OPEN) /* User want to use external RTIA */
lploop_cfg.LpAmpCfg.LpTiaSW = LPTIASW(13)|LPTIASW(2)|LPTIASW(4)|LPTIASW(5)|LPTIASW(9)/*|LPTIASW(10)*/; /* SW5/9 is closed to support external RTIA resistor */
else
lploop_cfg.LpAmpCfg.LpTiaSW = /*LPTIASW(13)|*/LPTIASW(2)|LPTIASW(4);
lploop_cfg.LpDacCfg.LpdacSel = LPDAC0;
lploop_cfg.LpDacCfg.DacData6Bit = (uint32_t)((AppSWVCfg.VzeroStart - 200.0f)/DAC6BITVOLT_1LSB);
lploop_cfg.LpDacCfg.DacData12Bit = (int32_t)((AppSWVCfg.RampStartVolt)/DAC12BITVOLT_1LSB) + lploop_cfg.LpDacCfg.DacData6Bit*64 ;
lploop_cfg.LpDacCfg.DataRst = bFALSE;
lploop_cfg.LpDacCfg.LpDacSW = LPDACSW_VBIAS2LPPA/*|LPDACSW_VBIAS2PIN*/|LPDACSW_VZERO2LPTIA/*|LPDACSW_VZERO2PIN*/;
lploop_cfg.LpDacCfg.LpDacRef = LPDACREF_2P5;
lploop_cfg.LpDacCfg.LpDacSrc = LPDACSRC_MMR;
lploop_cfg.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; /* Step Vbias. Use 12bit DAC ouput */
lploop_cfg.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT; /* Base is Vzero. Use 6 bit DAC ouput */
lploop_cfg.LpDacCfg.PowerEn = bTRUE;
AD5940_LPLoopCfgS(&lploop_cfg);
AD5940_StructInit(&dsp_cfg, sizeof(dsp_cfg));
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_LPTIA0_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_LPTIA0_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppSWVCfg.AdcPgaGain;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppSWVCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; /* ADC runs at 16MHz clock in this example, sample rate is 800kHz */
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE; /* We use data from SINC3 filter */
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = ADCSINC2OSR_1067; /* Don't care */
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_2; /* Don't care because it's disabled */
AD5940_DSPCfgS(&dsp_cfg);
/* 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 sequence generator here */
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
if(error == AD5940ERR_OK)
{
AD5940_StructInit(&AppSWVCfg.InitSeqInfo, sizeof(AppSWVCfg.InitSeqInfo));
if(SeqLen >= AppSWVCfg.MaxSeqLen)
return AD5940ERR_SEQLEN;
AppSWVCfg.InitSeqInfo.SeqId = SEQID_3;
AppSWVCfg.InitSeqInfo.SeqRamAddr = AppSWVCfg.SeqStartAddr;
AppSWVCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppSWVCfg.InitSeqInfo.SeqLen = SeqLen;
AppSWVCfg.InitSeqInfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&AppSWVCfg.InitSeqInfo);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
/**
* @brief Generate ADC control sequence and write the commands to SRAM.
* @return return error code.
*/
static AD5940Err AppSWVSeqADCCtrlGen(void)
{
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
ClksCalInfo_Type clks_cal;
clks_cal.DataCount = 1; /* Sample one point everytime */
clks_cal.DataType = DATATYPE_SINC3;
clks_cal.ADCSinc3Osr = AppSWVCfg.ADCSinc3Osr;
clks_cal.ADCSinc2Osr = ADCSINC2OSR_1067; /* Don't care */
clks_cal.ADCAvgNum = ADCAVGNUM_2; /* Don't care */
clks_cal.RatioSys2AdcClk = AppSWVCfg.SysClkFreq/AppSWVCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(AGPIO_Pin2);
AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16*100)); /* wait 250us for reference power up */
AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_ADCCNV, bFALSE); /* Stop ADC */
AD5940_SEQGpioCtrlS(0);
AD5940_EnterSleepS();/* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if(error == AD5940ERR_OK)
{
AD5940_StructInit(&AppSWVCfg.ADCSeqInfo, sizeof(AppSWVCfg.ADCSeqInfo));
if((SeqLen + AppSWVCfg.InitSeqInfo.SeqLen) >= AppSWVCfg.MaxSeqLen)
return AD5940ERR_SEQLEN;
AppSWVCfg.ADCSeqInfo.SeqId = SEQID_2;
AppSWVCfg.ADCSeqInfo.SeqRamAddr = AppSWVCfg.InitSeqInfo.SeqRamAddr + AppSWVCfg.InitSeqInfo.SeqLen ;
AppSWVCfg.ADCSeqInfo.pSeqCmd = pSeqCmd;
AppSWVCfg.ADCSeqInfo.SeqLen = SeqLen;
AppSWVCfg.ADCSeqInfo.WriteSRAM = bTRUE;
AD5940_SEQInfoCfg(&AppSWVCfg.ADCSeqInfo);
}
else
return error; /* Error */
return AD5940ERR_OK;
}
/**
* @brief Calculate DAC code step by step.
* @details The calculation is based on following variables.
* - RampStartVolt
* - RampPeakVolt
* - VzeroStart
* - VzeroPeak
* - StepNumber
* Below variables must be initialzed before call this function. It's done in function @ref AppSWVInit
* - RampState
* - CurrStepPos
* - bDACCodeInc
* - CurrRampCode
* @return return error code.
*/
static AD5940Err RampDacRegUpdate(uint32_t *pDACData)
{
uint32_t VbiasCode, VzeroCode;
if (AppSWVCfg.bRampOneDir)
{
if(AppSWVCfg.RampStartVolt > AppSWVCfg.RampPeakVolt)
AppSWVCfg.bDACCodeInc = bFALSE;
switch(AppSWVCfg.RampState)
{
case SWV_STATE0: /* Begin of Ramp */
AppSWVCfg.CurrVzeroCode = (uint32_t)((AppSWVCfg.VzeroStart - 200.0f)/DAC6BITVOLT_1LSB);
AppSWVCfg.RampState = SWV_STATE1;
break;
case SWV_STATE1:
if(AppSWVCfg.CurrStepPos >= AppSWVCfg.StepNumber/2)
{
AppSWVCfg.RampState = SWV_STATE2; /* Enter State2 */
AppSWVCfg.CurrVzeroCode = (uint32_t)((AppSWVCfg.VzeroPeak - 200.0f)/DAC6BITVOLT_1LSB);
}
break;
case SWV_STATE2:
if(AppSWVCfg.CurrStepPos >= AppSWVCfg.StepNumber)
AppSWVCfg.RampState = SWV_STOP; /* Enter Stop */
break;
case SWV_STOP:
break;
}
}
else
{
switch(AppSWVCfg.RampState)
{
case SWV_STATE0: /* Begin of Ramp */
AppSWVCfg.CurrVzeroCode = (uint32_t)((AppSWVCfg.VzeroStart - 200.0f)/DAC6BITVOLT_1LSB);
AppSWVCfg.RampState = SWV_STATE1;
break;
case SWV_STATE1:
if(AppSWVCfg.CurrStepPos >= AppSWVCfg.StepNumber/4)
{
AppSWVCfg.RampState = SWV_STATE2; /* Enter State2 */
AppSWVCfg.CurrVzeroCode = (uint32_t)((AppSWVCfg.VzeroPeak - 200.0f)/DAC6BITVOLT_1LSB);
}
break;
case SWV_STATE2:
if(AppSWVCfg.CurrStepPos >= (AppSWVCfg.StepNumber*2)/4)
{
AppSWVCfg.RampState = SWV_STATE3; /* Enter State2 */
AppSWVCfg.bDACCodeInc = AppSWVCfg.bDACCodeInc ? bFALSE : bTRUE;
}
break;
case SWV_STATE3:
if(AppSWVCfg.CurrStepPos >= (AppSWVCfg.StepNumber*3)/4)
{
AppSWVCfg.RampState = SWV_STATE4; /* Enter State2 */
AppSWVCfg.CurrVzeroCode = (uint32_t)((AppSWVCfg.VzeroPeak - 200.0f)/DAC6BITVOLT_1LSB);
}
break;
case SWV_STATE4:
if(AppSWVCfg.CurrStepPos >= (AppSWVCfg.StepNumber))
AppSWVCfg.RampState = SWV_STOP; /* Enter Stop */
break;
case SWV_STOP:
break;
}
}
AppSWVCfg.CurrStepPos++;
if(AppSWVCfg.bSqrWaveHiLevel)
{
if(AppSWVCfg.bDACCodeInc)
AppSWVCfg.CurrRampCode -= (AppSWVCfg.DACCodePerStep - AppSWVCfg.DACCodePerRamp);
else
AppSWVCfg.CurrRampCode -= AppSWVCfg.DACCodePerStep;
AppSWVCfg.bSqrWaveHiLevel = bFALSE;
}else
{
if(AppSWVCfg.bDACCodeInc)
AppSWVCfg.CurrRampCode += AppSWVCfg.DACCodePerStep;
else
AppSWVCfg.CurrRampCode += (AppSWVCfg.DACCodePerStep - AppSWVCfg.DACCodePerRamp);
AppSWVCfg.bSqrWaveHiLevel = bTRUE;
}
VzeroCode = AppSWVCfg.CurrVzeroCode;
VbiasCode = (uint32_t)(VzeroCode*64 + AppSWVCfg.CurrRampCode);
if(VbiasCode < (VzeroCode*64))
VbiasCode --;
/* Truncate */
if(VbiasCode > 4095) VbiasCode = 4095;
if(VzeroCode > 63) VzeroCode = 63;
*pDACData = (VzeroCode<<12)|VbiasCode;
return AD5940ERR_OK;
}
/* Geneate sequence(s) to update DAC step by step */
/* Note: this function doesn't need sequencer generator */
/**
* @brief Update DAC sequence in SRAM in real time.
* @details This function generates sequences to update DAC code step by step. It's also called in interrupt
* function when half commands in SRAM has been completed. We don't use sequence generator to save memory.
* Check more details from documentation of this example. @ref Ramp_Test_Example
* @return return error code
*
* */
static AD5940Err AppSWVSeqDACCtrlGen(void)
{
#define SEQLEN_ONESTEP 4L /* How many sequence commands are needed to update LPDAC. */
#define CURRBLK_BLK0 0 /* Current block is BLOCK0 */
#define CURRBLK_BLK1 1 /* Current block is BLOCK1 */
AD5940Err error = AD5940ERR_OK;
uint32_t BlockStartSRAMAddr;
uint32_t DACData, SRAMAddr;
uint32_t i;
uint32_t StepsThisBlock;
BoolFlag bIsFinalBlk;
uint32_t SeqCmdBuff[SEQLEN_ONESTEP];
/* All below static variables are inited in below 'if' block. They are only used in this function */
static BoolFlag bCmdForSeq0 = bTRUE;
static uint32_t DACSeqBlk0Addr, DACSeqBlk1Addr;
static uint32_t StepsRemainning, StepsPerBlock, DACSeqCurrBlk;
AppSWVCfg.StepNumber = (uint32_t)(2*(AppSWVCfg.RampPeakVolt - AppSWVCfg.RampStartVolt)/AppSWVCfg.SqrWvRampIncrement);
if(AppSWVCfg.bRampOneDir == bFALSE)
{
AppSWVCfg.StepNumber*=2;
AppSWVCfg.StepNumber-=2;
}
//AppSWVCfg.FifoThresh = AppSWVCfg.StepNumber;
if(AppSWVCfg.StepNumber >1020)
{
printf("Error: Selected Increment, StartVolt and PeakVolt exceed accepted limits \n");
while(1){}
}
/* Do some math calculations */
if(AppSWVCfg.bFirstDACSeq == bTRUE)
{
/* Reset bIsFirstRun at end of function. */
int32_t DACSeqLenMax;
StepsRemainning = AppSWVCfg.StepNumber;
DACSeqLenMax = (int32_t)AppSWVCfg.MaxSeqLen - (int32_t)AppSWVCfg.InitSeqInfo.SeqLen - (int32_t)AppSWVCfg.ADCSeqInfo.SeqLen;
if(DACSeqLenMax < SEQLEN_ONESTEP*4)
return AD5940ERR_SEQLEN; /* No enough sequencer SRAM available */
DACSeqLenMax -= SEQLEN_ONESTEP*2; /* Reserve commands each block */
StepsPerBlock = DACSeqLenMax/SEQLEN_ONESTEP/2;
DACSeqBlk0Addr = AppSWVCfg.ADCSeqInfo.SeqRamAddr + AppSWVCfg.ADCSeqInfo.SeqLen;
DACSeqBlk1Addr = DACSeqBlk0Addr + StepsPerBlock*SEQLEN_ONESTEP;
DACSeqCurrBlk = CURRBLK_BLK0;
/* Analog part */
AppSWVCfg.DACCodePerStep = AppSWVCfg.SqrWvAmplitude/DAC12BITVOLT_1LSB;
AppSWVCfg.DACCodePerRamp = AppSWVCfg.SqrWvRampIncrement/DAC12BITVOLT_1LSB;
#if ALIGIN_VOLT2LSB
AppSWVCfg.DACCodePerStep = (int32_t)AppSWVCfg.DACCodePerStep;
AppSWVCfg.DACCodePerRamp = (int32_t)AppSWVCfg.DACCodePerRamp;
#endif
if(AppSWVCfg.DACCodePerStep > 0)
AppSWVCfg.bDACCodeInc = bTRUE;
else
AppSWVCfg.bDACCodeInc = bFALSE;
AppSWVCfg.CurrRampCode = AppSWVCfg.RampStartVolt/DAC12BITVOLT_1LSB;
AppSWVCfg.RampState = SWV_STATE0; /* Init state to STATE0 */
AppSWVCfg.CurrStepPos = 0;
bCmdForSeq0 = bTRUE; /* Start with SEQ0 */
}
if(StepsRemainning == 0) return AD5940ERR_OK; /* Done. */
bIsFinalBlk = StepsRemainning <= StepsPerBlock?bTRUE:bFALSE;
if(bIsFinalBlk)
StepsThisBlock = StepsRemainning;
else
StepsThisBlock = StepsPerBlock;
StepsRemainning -= StepsThisBlock;
BlockStartSRAMAddr = (DACSeqCurrBlk == CURRBLK_BLK0)?\
DACSeqBlk0Addr:DACSeqBlk1Addr;
SRAMAddr = BlockStartSRAMAddr;
for(i=0; i<StepsThisBlock - 1; i++)
{
uint32_t CurrAddr = SRAMAddr;
SRAMAddr += SEQLEN_ONESTEP; /* Jump to next sequence */
RampDacRegUpdate(&DACData);
SeqCmdBuff[0] = SEQ_WR(REG_AFE_LPDACDAT0, DACData);
SeqCmdBuff[1] = SEQ_WAIT(10); /* !!!NOTE LPDAC need 10 clocks to update data. Before send AFE to sleep state, wait 10 extra clocks */
SeqCmdBuff[2] = SEQ_WR(bCmdForSeq0?REG_AFE_SEQ1INFO:REG_AFE_SEQ0INFO,\
(SRAMAddr<<BITP_AFE_SEQ1INFO_ADDR)|(SEQLEN_ONESTEP<<BITP_AFE_SEQ1INFO_LEN));
SeqCmdBuff[3] = SEQ_SLP();
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
bCmdForSeq0 = bCmdForSeq0?bFALSE:bTRUE;
}
/* Add final DAC update */
if(bIsFinalBlk)/* This is the final block */
{
uint32_t CurrAddr = SRAMAddr;
SRAMAddr += SEQLEN_ONESTEP; /* Jump to next sequence */
/* After update LPDAC with final data, we let sequencer to run 'final final' command, to disable sequencer. */
RampDacRegUpdate(&DACData);
SeqCmdBuff[0] = SEQ_WR(REG_AFE_LPDACDAT0, DACData);
SeqCmdBuff[1] = SEQ_WAIT(10); /* !!!NOTE LPDAC need 10 clocks to update data. Before send AFE to sleep state, wait 10 extra clocks */
SeqCmdBuff[2] = SEQ_WR(bCmdForSeq0?REG_AFE_SEQ1INFO:REG_AFE_SEQ0INFO,\
(SRAMAddr<<BITP_AFE_SEQ1INFO_ADDR)|(SEQLEN_ONESTEP<<BITP_AFE_SEQ1INFO_LEN));
SeqCmdBuff[3] = SEQ_SLP();
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
CurrAddr += SEQLEN_ONESTEP;
/* The final final command is to disable sequencer. */
SeqCmdBuff[0] = SEQ_NOP(); /* Do nothing */
SeqCmdBuff[1] = SEQ_NOP();
SeqCmdBuff[2] = SEQ_NOP();
SeqCmdBuff[3] = SEQ_STOP(); /* Stop sequencer. */
/* Disable sequencer, END of sequencer interrupt is generated. */
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
}
else /* This is not the final block */
{
/* Jump to next block. */
uint32_t CurrAddr = SRAMAddr;
SRAMAddr = (DACSeqCurrBlk == CURRBLK_BLK0)?\
DACSeqBlk1Addr:DACSeqBlk0Addr;
RampDacRegUpdate(&DACData);
SeqCmdBuff[0] = SEQ_WR(REG_AFE_LPDACDAT0, DACData);
SeqCmdBuff[1] = SEQ_WAIT(10);
SeqCmdBuff[2] = SEQ_WR(bCmdForSeq0?REG_AFE_SEQ1INFO:REG_AFE_SEQ0INFO,
(SRAMAddr<<BITP_AFE_SEQ1INFO_ADDR)|(SEQLEN_ONESTEP<<BITP_AFE_SEQ1INFO_LEN));
SeqCmdBuff[3] = SEQ_INT0(); /* Generate Custom interrupt 0. */
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
bCmdForSeq0 = bCmdForSeq0?bFALSE:bTRUE;
}
DACSeqCurrBlk = (DACSeqCurrBlk == CURRBLK_BLK0)?\
CURRBLK_BLK1:CURRBLK_BLK0; /* Switch between Block0 and block1 */
if(AppSWVCfg.bFirstDACSeq)
{
AppSWVCfg.bFirstDACSeq = bFALSE;
if(bIsFinalBlk == bFALSE)
{ /* Otherwise there is no need to init block1 sequence */
error = AppSWVSeqDACCtrlGen();
if(error != AD5940ERR_OK)
return error;
}
/* This is the first DAC sequence. */
AppSWVCfg.DACSeqInfo.SeqId = SEQID_0;
AppSWVCfg.DACSeqInfo.SeqLen = SEQLEN_ONESTEP;
AppSWVCfg.DACSeqInfo.SeqRamAddr = BlockStartSRAMAddr;
AppSWVCfg.DACSeqInfo.WriteSRAM = bFALSE; /* No need to write to SRAM. We already write them above. */
AD5940_SEQInfoCfg(&AppSWVCfg.DACSeqInfo);
}
return AD5940ERR_OK;
}
/**
* @brief Calibrate LPTIA internal RTIA resistor(s).
* @details This function will do calibration using parameters stored in @ref AppEDACfg structure.
* @return return error code.
*/
static AD5940Err AppSWVRtiaCal(void)
{
fImpPol_Type RtiaCalValue; /* Calibration result */
LPRTIACal_Type lprtia_cal;
AD5940_StructInit(&lprtia_cal, sizeof(lprtia_cal));
lprtia_cal.LpAmpSel = LPAMP0;
lprtia_cal.bPolarResult = bTRUE; /* Magnitude + Phase */
lprtia_cal.AdcClkFreq = AppSWVCfg.AdcClkFreq;
lprtia_cal.SysClkFreq = AppSWVCfg.SysClkFreq;
lprtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
lprtia_cal.ADCSinc2Osr = ADCSINC2OSR_22; /* Use SINC2 data as DFT data source */
lprtia_cal.DftCfg.DftNum = DFTNUM_2048; /* Maximum DFT number */
lprtia_cal.DftCfg.DftSrc = DFTSRC_SINC2NOTCH; /* @todo For frequency under 12Hz, need to optimize DFT source. Use SINC3 data as DFT source */
lprtia_cal.DftCfg.HanWinEn = bTRUE;
lprtia_cal.fFreq = AppSWVCfg.AdcClkFreq/4/22/2048*3; /* Sample 3 period of signal, 13.317Hz here. Do not use DC method, because it needs ADC/PGA calibrated firstly(but it's faster) */
lprtia_cal.fRcal = AppSWVCfg.RcalVal;
lprtia_cal.LpTiaRtia = AppSWVCfg.LPTIARtiaSel;
lprtia_cal.LpAmpPwrMod = LPAMPPWR_NORM;
lprtia_cal.bWithCtia = bFALSE;
AD5940_LPRtiaCal(&lprtia_cal, &RtiaCalValue);
AppSWVCfg.RtiaValue = RtiaCalValue;
printf("Rtia,%f,%f\n", RtiaCalValue.Magnitude, RtiaCalValue.Phase);
return AD5940ERR_OK;
}
/**
* @brief Initialize the ramp test. Call this functions every time before start ramp test.
* @param pBuffer: the buffer for sequencer generator. Only need to provide it for the first time.
* @param BufferSize: The buffer size start from pBuffer.
* @return return error code.
*/
AD5940Err AppSWVInit(uint32_t *pBuffer, uint32_t BufferSize)
{
AD5940Err error = AD5940ERR_OK;
FIFOCfg_Type fifo_cfg;
SEQCfg_Type seq_cfg;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Configure sequencer and stop it */
seq_cfg.SeqMemSize = SEQMEMSIZE_2KB;
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bFALSE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Start sequence generator */
/* Initialize sequencer generator */
if((AppSWVCfg.SWVInited == bFALSE)||\
(AppSWVCfg.bParaChanged == bTRUE))
{
if(pBuffer == 0) return AD5940ERR_PARA;
if(BufferSize == 0) return AD5940ERR_PARA;
if(AppSWVCfg.LPTIARtiaSel == LPTIARTIA_OPEN) /* Internal RTIA is opened. User wants to use external RTIA resistor */
{
AppSWVCfg.RtiaValue.Magnitude = AppSWVCfg.ExternalRtiaValue;
AppSWVCfg.RtiaValue.Phase = 0;
}
else
AppSWVRtiaCal();
AppSWVCfg.SWVInited = bFALSE;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate sequence and write them to SRAM start from address AppSWVCfg.SeqStartAddr */
error = AppSWVSeqInitGen(); /* Application initialization sequence */
if(error != AD5940ERR_OK) return error;
error = AppSWVSeqADCCtrlGen(); /* ADC control sequence */
if(error != AD5940ERR_OK) return error;
AppSWVCfg.bParaChanged = bFALSE; /* Clear this flag as we already implemented the new configuration */
}
/* Reconfigure FIFO, The Rtia calibration function may generate data that stored to FIFO */
AD5940_FIFOCtrlS(FIFOSRC_SINC3, bFALSE); /* Disable FIFO firstly */
fifo_cfg.FIFOEn = bTRUE;
fifo_cfg.FIFOSrc = FIFOSRC_SINC3;
fifo_cfg.FIFOThresh = AppSWVCfg.FifoThresh; /* Change FIFO paramters */
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
AD5940_FIFOCfg(&fifo_cfg);
/* Clear all interrupts */
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Generate DAC sequence */
AppSWVCfg.bFirstDACSeq = bTRUE;
error = AppSWVSeqDACCtrlGen();
if(error != AD5940ERR_OK) return error;
/* Configure sequence info. */
AppSWVCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppSWVCfg.InitSeqInfo);
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer */
AD5940_SEQMmrTrig(AppSWVCfg.InitSeqInfo.SeqId);
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ);
AppSWVCfg.ADCSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppSWVCfg.ADCSeqInfo);
AppSWVCfg.DACSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppSWVCfg.DACSeqInfo);
AD5940_SEQCtrlS(bFALSE);
AD5940_WriteReg(REG_AFE_SEQCNT, 0);
AD5940_SEQCtrlS(bTRUE); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ); /* Set to low power mode */
AppSWVCfg.SWVInited = bTRUE; /* SWV application has been initialized. */
return AD5940ERR_OK;
}
/**
* @brief This function is called in ISR when AFE has been wakeup and we can access registers.
* @param pData: the buffer points to data read back from FIFO. Not needed for this application-SWV
* @param pDataCount: The data count in pData buffer.
* @return return error code.
*/
static int32_t AppSWVRegModify(int32_t * const pData, uint32_t *pDataCount)
{
if(AppSWVCfg.StopRequired == bTRUE)
{
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
return AD5940ERR_OK;
}
/**
* @brief Depending on the data type, do appropriate data pre-process before return back to controller
* @param pData: the buffer points to data read back from FIFO. Not needed for this application-SWV
* @param pDataCount: The data count in pData buffer.
* @return return error code.
*/
static int32_t AppSWVDataProcess(int32_t * const pData, uint32_t *pDataCount)
{
uint32_t i, datacount;
datacount = *pDataCount;
float *pOut = (float *)pData;
for(i=0;i<datacount;i++)
{
pData[i] &= 0xffff;
pOut[i] = AD5940_ADCCode2Volt(pData[i], AppSWVCfg.AdcPgaGain, AppSWVCfg.ADCRefVolt)/AppSWVCfg.RtiaValue.Magnitude * 1e3f; /* Result unit is uA. */
}
return 0;
}
/**
* @brief The interrupt service routine for SWV test.
* @param pBuff: The buffer provides by host, used to store data read back from FIFO.
* @param pCount: The available buffer size starts from pBuff.
* @return return error code.
*/
AD5940Err AppSWVISR(void *pBuff, uint32_t *pCount)
{
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
uint32_t IntFlag;
if(AD5940_WakeUp(10) > 10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK);
*pCount = 0;
IntFlag = AD5940_INTCGetFlag(AFEINTC_0);
if(IntFlag & AFEINTSRC_CUSTOMINT0) /* High priority. */
{
AD5940Err error;
AD5940_INTCClrFlag(AFEINTSRC_CUSTOMINT0);
//AD5940_McuSetHigh();
error = AppSWVSeqDACCtrlGen();
//AD5940_McuSetLow();
if(error != AD5940ERR_OK) return error;
// AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
//AD5940_EnterSleepS(); /* If there is need to do AFE re-configure, do it here when AFE is in active state */
}
if(IntFlag&AFEINTSRC_DATAFIFOTHRESH)
{
FifoCnt = AD5940_FIFOGetCnt();
if(FifoCnt > BuffCount)
{
///@todo buffer is limited.
}
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppSWVRegModify(pBuff, &FifoCnt);
// AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
//AD5940_EnterSleepS();
/* Process data */
AppSWVDataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
return 0;
}
if(IntFlag & AFEINTSRC_ENDSEQ)
{
FifoCnt = AD5940_FIFOGetCnt();
AD5940_INTCClrFlag(AFEINTSRC_ENDSEQ);
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
/* Process data */
AppSWVDataProcess((int32_t*)pBuff,&FifoCnt);
*pCount = FifoCnt;
AppSWVCtrl(APPCTRL_STOPNOW, 0); /* Stop the Wakeup Timer. */
}
return 0;
}
/**
* @}
* @}
*/

View File

@ -0,0 +1,94 @@
/*!
*****************************************************************************
@file: RampTest.H
@author: $Author: nxu2 $
@brief: Ramp Test header file.
@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.
*****************************************************************************/
#ifndef _SWVTEST_H_
#define _SWVTEST_H_
#include "ad5940.h"
#include <stdio.h>
#include "string.h"
#include "math.h"
/* Do not modify following parameters */
#define ALIGIN_VOLT2LSB 0 /* Set it to 1 to align each voltage step to 1LSB of DAC. 0: step code is fractional. */
#define DAC12BITVOLT_1LSB (2200.0f/4095) //mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) //mV
/**
* The Ramp application related paramter structure
*/
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; /**< Not used for Ramp.Calibration sequence start address in SRAM of AD5940 */
uint32_t MaxSeqLenCal; /**< Not used for Ramp. */
/* Application related parameters */
float LFOSCClkFreq; /**< 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 */
float ADCRefVolt; /**< The real ADC voltage in mV. */
/* Describe Ramp signal */
float RampStartVolt; /**< The start voltage of ramp signal in mV */
float RampPeakVolt; /**< The maximum or minimum voltage of ramp in mV */
float VzeroStart; /**< The start voltage of Vzero in mV. Set it to 2400mV by default */
float VzeroPeak; /**< The peak voltage of Vzero in mV. Set it to 200mV by default */
uint32_t StepNumber; /**< Total number of steps. Limited to 4095. */
/* Receive path configuration */
float SampleDelay; /**< The time delay between update DAC and start ADC */
uint32_t LPTIARtiaSel; /**< Select RTIA */
float ExternalRtiaValue; /**< The optional external RTIA value in Ohm. Disconnect internal RTIA to use external RTIA. When using internal RTIA, this value is ignored. */
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; /**< We use data from SINC3 filter. */
/* Digital related */
uint32_t FifoThresh; /**< FIFO Threshold value */
/* Private variables for internal usage */
BoolFlag SWVInited; /**< If the program run firstly, generated initialization sequence commands */
fImpPol_Type RtiaValue; /**< Calibrated Rtia value */
SEQInfo_Type InitSeqInfo;
SEQInfo_Type ADCSeqInfo;
BoolFlag bFirstDACSeq; /**< Init DAC sequence */
SEQInfo_Type DACSeqInfo; /**< The first DAC update sequence info */
uint32_t CurrStepPos; /**< Current position */
float DACCodePerStep; /**< DAC codes in square waveform */
float DACCodePerRamp; /**< DAC codes needed to ramp increment */
float CurrRampCode; /**< */
float Frequency; /**< Frequency of square wave */
float SqrWvAmplitude; /**< Set amplitude of square wave */
float SqrWvRampIncrement; /**< Ramp increase in mV */
uint32_t CurrVzeroCode;
BoolFlag bDACCodeInc; /**< Increase DAC code. */
BoolFlag bSqrWaveHiLevel; /**< Flag to indicate square wave high level */
BoolFlag bRampOneDir; /**< Ramp in one direction only */
BoolFlag StopRequired; /**< After FIFO is ready, stop the measurement sequence */
enum _RampState{SWV_STATE0 = 0, SWV_STATE1, SWV_STATE2, SWV_STATE3, SWV_STATE4, SWV_STOP} RampState;
}AppSWVCfg_Type;
#define APPCTRL_START 0
#define APPCTRL_STOPNOW 1
#define APPCTRL_STOPSYNC 2
#define APPCTRL_SHUTDOWN 3 /**< Note: shutdown here means turn off everything and put AFE to hibernate mode. The word 'SHUT DOWN' is only used here. */
AD5940Err AppSWVInit(uint32_t *pBuffer, uint32_t BufferSize);
AD5940Err AppSWVGetCfg(void *pCfg);
AD5940Err AppSWVISR(void *pBuff, uint32_t *pCount);
AD5940Err AppSWVCtrl(uint32_t Command, void *pPara);
void AD5940_McuSetLow(void);
void AD5940_McuSetHigh(void);
#endif

198
examples/UARTCmd.c.txt Normal file
View File

@ -0,0 +1,198 @@
/*!
*****************************************************************************
@file: UARTCmd.c
@author: $Author: nxu2 $
@brief: UART Command process
@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 "stdint.h"
#include "string.h"
#include "stdio.h"
#include <stdlib.h>
#define LINEBUFF_SIZE 128
#define CMDTABLE_SIZE 8
uint32_t help(uint32_t para1, uint32_t para2);
uint32_t say_hello(uint32_t para1, uint32_t para2);
uint32_t rst_eda_base(uint32_t para1, uint32_t para2);
uint32_t set_eda_base(uint32_t para1, uint32_t para2);
uint32_t get_average_imp(uint32_t para1, uint32_t para2);
uint32_t eda_start(uint32_t para1, uint32_t para2);
uint32_t eda_stop(uint32_t para1, uint32_t para2);
struct __uartcmd_table
{
void *pObj;
const char *cmd_name;
const char *pDesc;
}uart_cmd_table[CMDTABLE_SIZE]=
{
{(void*)help, "help", "print supported commands"},
{(void*)help, "?", "print supported commands"},
{(void*)say_hello, "hello", "print parameteres and say hello"},
{(void*)eda_start, "edastart", "Start EDA measurement"},
{(void*)eda_stop, "edastop", "Stop EDA measurement immediately"},
{(void*)rst_eda_base, "rstbase", "Reset EDA baseline impedance"},
{(void*)set_eda_base, "setbase", "Set EDA impedance baseline with current averaged impedance result"},
{(void*)get_average_imp, "getavr", "get average impedance of all result"},
};
uint32_t help(uint32_t para1, uint32_t para2)
{
int i = 0;
printf("*****help menu*****\nbelow are supported commands:\n");
for(;i<CMDTABLE_SIZE;i++)
{
if(uart_cmd_table[i].pObj)
printf("%-8s --\t%s\n", uart_cmd_table[i].cmd_name, uart_cmd_table[i].pDesc);
}
printf("***table end***\n");
return 0x87654321;
}
uint32_t say_hello(uint32_t para1, uint32_t para2)
{
printf("para1:0x%08x, para2:0x%08x\n", para1, para2);
printf("Hello\n");
return 0x12345678;
}
char line_buffer[LINEBUFF_SIZE];
uint32_t line_buffer_index = 0;
uint32_t token_count = 0;
void *pObjFound = 0;
uint32_t parameter1, parameter2;
void UARTCmd_RemoveSpaces(void)
{
int i = 0;
token_count = 0;
char flag_found_token = 0;
while(i<line_buffer_index)
{
if(line_buffer[i] == ' ') line_buffer[i] = '\0';
else break;
i++;
}
if(i == line_buffer_index) return; /* All spaces... */
while(i<line_buffer_index)
{
if(line_buffer[i] == ' ')
{
line_buffer[i] = '\0';
flag_found_token = 0;
}
else
{
if(flag_found_token == 0)
token_count ++;
flag_found_token = 1;
}
i++;
}
}
void UARTCmd_MatchCommand(void)
{
char *pcmd;
int i = 0;
pObjFound = 0;
while(i<line_buffer_index)
{
if(line_buffer[i] != '\0')
{
pcmd = &line_buffer[i];
break;
}
i++;
}
for(i=0;i<CMDTABLE_SIZE;i++)
{
if(strcmp(uart_cmd_table[i].cmd_name, pcmd) == 0)
{
/* Found you! */
pObjFound = uart_cmd_table[i].pObj;
break;
}
}
}
/* Translate string 'p' to number, store results in 'Res', return error code */
static uint32_t Str2Num(char *s, uint32_t *Res)
{
char *p;
unsigned int base=10;
*Res = strtoul( s, &p, base );
return 0;
}
void UARTCmd_TranslateParas(void)
{
char *p = line_buffer;
parameter1 = 0;
parameter2 = 0;
while(*p == '\0') p++; /* goto command */
while(*p != '\0') p++; /* skip command. */
while(*p == '\0') p++; /* goto first parameter */
if(Str2Num(p, &parameter1) != 0) return;
if(token_count == 2) return; /* Only one parameter */
while(*p != '\0') p++; /* skip first command. */
while(*p == '\0') p++; /* goto second parameter */
Str2Num(p, &parameter2);
}
void UARTCmd_Process(char c)
{
if(line_buffer_index >= LINEBUFF_SIZE-1)
line_buffer_index = 0; /* Error: buffer overflow */
if( (c == '\r') || (c == '\n'))
{
uint32_t res;
line_buffer[line_buffer_index] = '\0';
/* Start to process command */
if(line_buffer_index == 0)
{
line_buffer_index = 0; /* Reset buffer */
return; /* No command inputs, return */
}
/* Step1, remove space */
UARTCmd_RemoveSpaces();
if(token_count == 0)
{
line_buffer_index = 0; /* Reset buffer */
return; /* No valid input */
}
/* Step2, match commands */
UARTCmd_MatchCommand();
if(pObjFound == 0)
{
line_buffer_index = 0; /* Reset buffer */
return; /* Command not support */
}
if(token_count > 1) /* There is parameters */
{
UARTCmd_TranslateParas();
}
/* Step3, call function */
res = ((uint32_t (*)(uint32_t, uint32_t))(pObjFound))(parameter1, parameter2);
printf("res:0x%08x\n", res);
line_buffer_index = 0; /* Reset buffer */
}
else
{
line_buffer[line_buffer_index++] = c;
}
}

41
examples/main.c.txt Normal file
View File

@ -0,0 +1,41 @@
/*
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 "ADuCM3029.H"
#include "AD5940PORT.h"
#include "stdio.h"
int main(void)
{
void AD5940_Main(void);
MCUPlatformInit(0);
MCUGpioInit(0);
MCUExtiInit(0);
MCUSPIInit(0);
MCUSysTickInit(0);
printf("Hello AD5940-Build Time:%s\n",__TIME__);
AD5940_Main();
}
//void Host_EnterHibernate(void)
//{
// int32_t index = 0;
// uint32_t savedWDT;
// savedWDT = pADI_WDT0->CTL; //None of the watchdog timer registers are retained in hibernate mode
// SCB->SCR = 0x04; // sleepdeep mode - write to the Cortex-m3 System Control register bit2
// pADI_PMG0->PWRKEY = 0x4859; // key1
// pADI_PMG0->PWRMOD = ENUM_PMG_PWRMOD_HIBERNATE|BITM_PMG_PWRMOD_MONVBATN;
// for (index=0;index<2;index++);
// __WFI();
// for (index=0;index<2;index++);
// pADI_WDT0->CTL = savedWDT; //restore WDT control register.
// UrtCfg(230400);/*Baud rate: 230400*/
// SpiMasterInit();
//}

View File

@ -1,484 +1,534 @@
// File: host/src/GraphWidget.cpp
#include "GraphWidget.h"
#include <limits>
#include <cmath>
#include <QMenu>
#include <QInputDialog>
#include <QFormLayout>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QInputDialog>
#include <QMenu>
#include <cmath>
#include <limits>
// Simple Linear Regression Helper
static void linearRegression(const QVector<double>& x, const QVector<double>& y, double &m, double &c) {
double sumX=0, sumY=0, sumXY=0, sumX2=0;
int n = x.size();
for(int i=0; i<n; i++) {
sumX += x[i];
sumY += y[i];
sumXY += x[i]*y[i];
sumX2 += x[i]*x[i];
}
m = (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX);
c = (sumY - m*sumX) / n;
static void linearRegression(const QVector<double> &x, const QVector<double> &y,
double &m, double &c) {
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
int n = x.size();
for (int i = 0; i < n; i++) {
sumX += x[i];
sumY += y[i];
sumXY += x[i] * y[i];
sumX2 += x[i] * x[i];
}
m = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
c = (sumY - m * sumX) / n;
}
GraphWidget::GraphWidget(QWidget *parent) : QWidget(parent) {
mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0,0,0,0);
mainLayout->setSpacing(0);
mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// Toolbar
toolbar = new QWidget(this);
toolbar->setStyleSheet("background-color: #2D2D2D;");
QHBoxLayout *toolLayout = new QHBoxLayout(toolbar);
toolLayout->setContentsMargins(5, 2, 5, 2);
// Toolbar
toolbar = new QWidget(this);
toolbar->setStyleSheet("background-color: #2D2D2D;");
QHBoxLayout *toolLayout = new QHBoxLayout(toolbar);
toolLayout->setContentsMargins(5, 2, 5, 2);
btnScaleX = new QPushButton("Scale X", this);
btnScaleY = new QPushButton("Scale Y", this);
btnScaleBoth = new QPushButton("Scale Both", this);
btnCenter = new QPushButton("Center", this);
btnAnalyze = new QPushButton("Analyze", this);
btnScaleX = new QPushButton("Scale X", this);
btnScaleY = new QPushButton("Scale Y", this);
btnScaleBoth = new QPushButton("Scale Both", this);
btnCenter = new QPushButton("Center", this);
btnAnalyze = new QPushButton("Analyze", this);
QString btnStyle = "QPushButton { background-color: #444; color: white; border: 1px solid #555; padding: 3px 8px; border-radius: 3px; } QPushButton:hover { background-color: #555; }";
btnScaleX->setStyleSheet(btnStyle);
btnScaleY->setStyleSheet(btnStyle);
btnScaleBoth->setStyleSheet(btnStyle);
btnCenter->setStyleSheet(btnStyle);
btnAnalyze->setStyleSheet(btnStyle);
QString btnStyle = "QPushButton { background-color: #444; color: white; "
"border: 1px solid #555; padding: 3px 8px; border-radius: "
"3px; } QPushButton:hover { background-color: #555; }";
btnScaleX->setStyleSheet(btnStyle);
btnScaleY->setStyleSheet(btnStyle);
btnScaleBoth->setStyleSheet(btnStyle);
btnCenter->setStyleSheet(btnStyle);
btnAnalyze->setStyleSheet(btnStyle);
toolLayout->addWidget(btnScaleX);
toolLayout->addWidget(btnScaleY);
toolLayout->addWidget(btnScaleBoth);
toolLayout->addWidget(btnCenter);
toolLayout->addStretch();
toolLayout->addWidget(btnAnalyze);
toolLayout->addWidget(btnScaleX);
toolLayout->addWidget(btnScaleY);
toolLayout->addWidget(btnScaleBoth);
toolLayout->addWidget(btnCenter);
toolLayout->addStretch();
toolLayout->addWidget(btnAnalyze);
mainLayout->addWidget(toolbar);
mainLayout->addWidget(toolbar);
plot = new QCustomPlot(this);
mainLayout->addWidget(plot);
plot = new QCustomPlot(this);
mainLayout->addWidget(plot);
plot->setBackground(QBrush(QColor(25, 25, 25)));
plot->setBackground(QBrush(QColor(25, 25, 25)));
auto styleAxis = [](QCPAxis *axis) {
axis->setBasePen(QPen(Qt::white));
axis->setTickPen(QPen(Qt::white));
axis->setSubTickPen(QPen(Qt::white));
axis->setTickLabelColor(Qt::white);
axis->setLabelColor(Qt::white);
axis->grid()->setPen(QPen(QColor(60, 60, 60), 0, Qt::DotLine));
axis->grid()->setSubGridVisible(true);
axis->grid()->setSubGridPen(QPen(QColor(40, 40, 40), 0, Qt::DotLine));
};
auto styleAxis = [](QCPAxis *axis) {
axis->setBasePen(QPen(Qt::white));
axis->setTickPen(QPen(Qt::white));
axis->setSubTickPen(QPen(Qt::white));
axis->setTickLabelColor(Qt::white);
axis->setLabelColor(Qt::white);
axis->grid()->setPen(QPen(QColor(60, 60, 60), 0, Qt::DotLine));
axis->grid()->setSubGridVisible(true);
axis->grid()->setSubGridPen(QPen(QColor(40, 40, 40), 0, Qt::DotLine));
};
styleAxis(plot->xAxis);
styleAxis(plot->yAxis);
styleAxis(plot->yAxis2);
styleAxis(plot->xAxis);
styleAxis(plot->yAxis);
styleAxis(plot->yAxis2);
// --- Setup Graphs ---
graphReal = plot->addGraph();
graphReal->setPen(QPen(QColor(0, 255, 255), 2));
graphReal->setLineStyle(QCPGraph::lsLine);
graphReal->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QColor(0, 255, 255), 3));
// --- Setup Graphs ---
graphReal = plot->addGraph();
graphReal->setPen(QPen(QColor(0, 255, 255), 2));
graphReal->setLineStyle(QCPGraph::lsLine);
graphReal->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssCircle, QColor(0, 255, 255), 3));
graphImag = plot->addGraph(plot->xAxis, plot->yAxis2);
graphImag->setPen(QPen(QColor(255, 0, 255), 2));
graphImag->setLineStyle(QCPGraph::lsLine);
graphImag->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssTriangle, QColor(255, 0, 255), 3));
graphImag = plot->addGraph(plot->xAxis, plot->yAxis2);
graphImag->setPen(QPen(QColor(255, 0, 255), 2));
graphImag->setLineStyle(QCPGraph::lsLine);
graphImag->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssTriangle, QColor(255, 0, 255), 3));
graphHilbert = plot->addGraph(plot->xAxis, plot->yAxis2);
QPen pen3(Qt::green); pen3.setWidth(2); pen3.setStyle(Qt::DashLine);
graphHilbert->setPen(pen3);
graphHilbert = plot->addGraph(plot->xAxis, plot->yAxis2);
QPen pen3(Qt::green);
pen3.setWidth(2);
pen3.setStyle(Qt::DashLine);
graphHilbert->setPen(pen3);
graphNyquistCorr = plot->addGraph(plot->xAxis, plot->yAxis);
graphNyquistCorr->setPen(QPen(QColor(255, 165, 0), 2));
graphNyquistCorr->setLineStyle(QCPGraph::lsLine);
graphNyquistCorr->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCross, 4));
graphNyquistCorr->setName("De-embedded (True Cell)");
graphNyquistCorr = plot->addGraph(plot->xAxis, plot->yAxis);
graphNyquistCorr->setPen(QPen(QColor(255, 165, 0), 2));
graphNyquistCorr->setLineStyle(QCPGraph::lsLine);
graphNyquistCorr->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssCross, 4));
graphNyquistCorr->setName("De-embedded (True Cell)");
graphAmp = plot->addGraph();
graphAmp->setPen(QPen(QColor(50, 255, 50), 2));
graphAmp->setLineStyle(QCPGraph::lsLine);
graphAmp->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 3));
graphAmp->setName("Current");
graphAmp = plot->addGraph();
graphAmp->setPen(QPen(QColor(50, 255, 50), 2));
graphAmp->setLineStyle(QCPGraph::lsLine);
graphAmp->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 3));
graphAmp->setName("Current");
graphExtrapolated = plot->addGraph(plot->xAxis, plot->yAxis);
graphExtrapolated->setLineStyle(QCPGraph::lsNone);
graphExtrapolated->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssStar, QColor(255, 215, 0), 12));
graphExtrapolated->setPen(QPen(QColor(255, 215, 0), 3));
graphExtrapolated->setName("Rs (Extrapolated)");
graphExtrapolated = plot->addGraph(plot->xAxis, plot->yAxis);
graphExtrapolated->setLineStyle(QCPGraph::lsNone);
graphExtrapolated->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssStar, QColor(255, 215, 0), 12));
graphExtrapolated->setPen(QPen(QColor(255, 215, 0), 3));
graphExtrapolated->setName("Rs (Extrapolated)");
graphLSVBlank = plot->addGraph();
QPen penBlank(QColor(150, 150, 150)); penBlank.setWidth(2); penBlank.setStyle(Qt::DashLine);
graphLSVBlank->setPen(penBlank);
graphLSVBlank->setName("Blank (Tap Water)");
graphLSVBlank = plot->addGraph();
QPen penBlank(QColor(150, 150, 150));
penBlank.setWidth(2);
penBlank.setStyle(Qt::DashLine);
graphLSVBlank->setPen(penBlank);
graphLSVBlank->setName("Blank (Tap Water)");
graphLSVSample = plot->addGraph();
graphLSVSample->setPen(QPen(Qt::yellow, 2));
graphLSVSample->setName("Sample (Bleach)");
graphLSVSample = plot->addGraph();
graphLSVSample->setPen(QPen(Qt::yellow, 2));
graphLSVSample->setName("Sample (Bleach)");
graphLSVDiff = plot->addGraph();
graphLSVDiff->setPen(QPen(Qt::cyan, 3));
graphLSVDiff->setName("Diff (Chlorine)");
graphLSVDiff = plot->addGraph();
graphLSVDiff->setPen(QPen(Qt::cyan, 3));
graphLSVDiff->setName("Diff (Chlorine)");
graphFit = plot->addGraph();
graphFit->setPen(QPen(Qt::red, 2));
graphFit->setName("Fit");
graphFit = plot->addGraph();
graphFit->setPen(QPen(Qt::red, 2));
graphFit->setName("Fit");
graphNyquistRaw = graphReal;
graphNyquistRaw = graphReal;
plot->yAxis2->setVisible(true);
plot->yAxis2->setTickLabels(true);
plot->yAxis2->setVisible(true);
plot->yAxis2->setTickLabels(true);
connect(plot->yAxis, SIGNAL(rangeChanged(QCPRange)), plot->yAxis2, SLOT(setRange(QCPRange)));
connect(plot->yAxis2, SIGNAL(rangeChanged(QCPRange)), plot->yAxis, SLOT(setRange(QCPRange)));
connect(plot->yAxis, SIGNAL(rangeChanged(QCPRange)), plot->yAxis2,
SLOT(setRange(QCPRange)));
connect(plot->yAxis2, SIGNAL(rangeChanged(QCPRange)), plot->yAxis,
SLOT(setRange(QCPRange)));
plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom |
QCP::iSelectPlottables);
// Right click drag to zoom
plot->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical);
plot->axisRect()->setRangeZoom(Qt::Horizontal | Qt::Vertical);
// Right click drag to zoom
plot->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical);
plot->axisRect()->setRangeZoom(Qt::Horizontal | Qt::Vertical);
plot->legend->setVisible(true);
QFont legendFont = font();
legendFont.setPointSize(9);
plot->legend->setFont(legendFont);
plot->legend->setBrush(QBrush(QColor(40, 40, 40, 200)));
plot->legend->setBorderPen(QPen(Qt::white));
plot->legend->setTextColor(Qt::white);
plot->legend->setVisible(true);
QFont legendFont = font();
legendFont.setPointSize(9);
plot->legend->setFont(legendFont);
plot->legend->setBrush(QBrush(QColor(40, 40, 40, 200)));
plot->legend->setBorderPen(QPen(Qt::white));
plot->legend->setTextColor(Qt::white);
selectionRect = new QCPItemRect(plot);
selectionRect->setPen(QPen(Qt::yellow, 1, Qt::DashLine));
selectionRect->setBrush(QBrush(QColor(255, 255, 0, 50)));
selectionRect->setVisible(false);
selectionRect = new QCPItemRect(plot);
selectionRect->setPen(QPen(Qt::yellow, 1, Qt::DashLine));
selectionRect->setBrush(QBrush(QColor(255, 255, 0, 50)));
selectionRect->setVisible(false);
connect(btnScaleX, &QPushButton::clicked, this, &GraphWidget::scaleX);
connect(btnScaleY, &QPushButton::clicked, this, &GraphWidget::scaleY);
connect(btnScaleBoth, &QPushButton::clicked, this, &GraphWidget::scaleBoth);
connect(btnCenter, &QPushButton::clicked, this, &GraphWidget::centerView);
connect(btnAnalyze, &QPushButton::clicked, this, &GraphWidget::startAnalyze);
connect(btnScaleX, &QPushButton::clicked, this, &GraphWidget::scaleX);
connect(btnScaleY, &QPushButton::clicked, this, &GraphWidget::scaleY);
connect(btnScaleBoth, &QPushButton::clicked, this, &GraphWidget::scaleBoth);
connect(btnCenter, &QPushButton::clicked, this, &GraphWidget::centerView);
connect(btnAnalyze, &QPushButton::clicked, this, &GraphWidget::startAnalyze);
// Custom mouse handling for selection
connect(plot, &QCustomPlot::mousePress, [this](QMouseEvent *event){
if(isSelecting && event->button() == Qt::LeftButton) {
selStartX = plot->xAxis->pixelToCoord(event->pos().x());
selStartY = plot->yAxis->pixelToCoord(event->pos().y());
selectionRect->topLeft->setCoords(selStartX, selStartY);
selectionRect->bottomRight->setCoords(selStartX, selStartY);
selectionRect->setVisible(true);
plot->replot();
// Custom mouse handling for selection
connect(plot, &QCustomPlot::mousePress, [this](QMouseEvent *event) {
if (isSelecting && event->button() == Qt::LeftButton) {
selStartX = plot->xAxis->pixelToCoord(event->pos().x());
selStartY = plot->yAxis->pixelToCoord(event->pos().y());
selectionRect->topLeft->setCoords(selStartX, selStartY);
selectionRect->bottomRight->setCoords(selStartX, selStartY);
selectionRect->setVisible(true);
plot->replot();
}
});
connect(plot, &QCustomPlot::mouseMove, [this](QMouseEvent *event) {
if (isSelecting && (event->buttons() & Qt::LeftButton)) {
double x = plot->xAxis->pixelToCoord(event->pos().x());
double y = plot->yAxis->pixelToCoord(event->pos().y());
selectionRect->bottomRight->setCoords(x, y);
plot->replot();
}
});
connect(plot, &QCustomPlot::mouseRelease, [this](QMouseEvent *event) {
if (isSelecting && event->button() == Qt::LeftButton) {
isSelecting = false;
plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
double x2 = plot->xAxis->pixelToCoord(event->pos().x());
double y2 = plot->yAxis->pixelToCoord(event->pos().y());
// Collect data in rect
QVector<double> xData, yData;
double minX = std::min(selStartX, x2);
double maxX = std::max(selStartX, x2);
double minY = std::min(selStartY, y2);
double maxY = std::max(selStartY, y2);
// Iterate visible graphs to find data
QList<QCPGraph *> graphs = {graphReal, graphAmp, graphLSVSample,
graphLSVDiff};
for (auto g : graphs) {
if (!g->visible())
continue;
for (auto it = g->data()->begin(); it != g->data()->end(); ++it) {
if (it->key >= minX && it->key <= maxX && it->value >= minY &&
it->value <= maxY) {
xData.append(it->key);
yData.append(it->value);
}
}
});
if (!xData.isEmpty())
break; // Only fit one graph
}
connect(plot, &QCustomPlot::mouseMove, [this](QMouseEvent *event){
if(isSelecting && (event->buttons() & Qt::LeftButton)) {
double x = plot->xAxis->pixelToCoord(event->pos().x());
double y = plot->yAxis->pixelToCoord(event->pos().y());
selectionRect->bottomRight->setCoords(x, y);
plot->replot();
if (xData.size() > 2) {
// Show Dialog
QDialog dlg(this);
dlg.setWindowTitle("Fit Data");
QFormLayout *layout = new QFormLayout(&dlg);
QComboBox *type = new QComboBox();
type->addItems({"Linear", "Exponential", "Logarithmic", "Polynomial"});
QSpinBox *order = new QSpinBox();
order->setRange(1, 10);
order->setValue(2);
QCheckBox *inverse = new QCheckBox("Inverse");
layout->addRow("Type:", type);
layout->addRow("Poly Order:", order);
layout->addRow(inverse);
QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok |
QDialogButtonBox::Cancel);
layout->addRow(btns);
connect(btns, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
connect(btns, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
if (dlg.exec() == QDialog::Accepted) {
performFit(xData, yData, type->currentIndex(), order->value(),
inverse->isChecked());
}
});
}
connect(plot, &QCustomPlot::mouseRelease, [this](QMouseEvent *event){
if(isSelecting && event->button() == Qt::LeftButton) {
isSelecting = false;
plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
selectionRect->setVisible(false);
plot->replot();
}
});
double x2 = plot->xAxis->pixelToCoord(event->pos().x());
double y2 = plot->yAxis->pixelToCoord(event->pos().y());
// Collect data in rect
QVector<double> xData, yData;
double minX = std::min(selStartX, x2);
double maxX = std::max(selStartX, x2);
double minY = std::min(selStartY, y2);
double maxY = std::max(selStartY, y2);
// Iterate visible graphs to find data
QList<QCPGraph*> graphs = {graphReal, graphAmp, graphLSVSample, graphLSVDiff};
for(auto g : graphs) {
if(!g->visible()) continue;
for(auto it = g->data()->begin(); it != g->data()->end(); ++it) {
if(it->key >= minX && it->key <= maxX && it->value >= minY && it->value <= maxY) {
xData.append(it->key);
yData.append(it->value);
}
}
if(!xData.isEmpty()) break; // Only fit one graph
}
if(xData.size() > 2) {
// Show Dialog
QDialog dlg(this);
dlg.setWindowTitle("Fit Data");
QFormLayout *layout = new QFormLayout(&dlg);
QComboBox *type = new QComboBox();
type->addItems({"Linear", "Exponential", "Logarithmic", "Polynomial"});
QSpinBox *order = new QSpinBox(); order->setRange(1, 10); order->setValue(2);
QCheckBox *inverse = new QCheckBox("Inverse");
layout->addRow("Type:", type);
layout->addRow("Poly Order:", order);
layout->addRow(inverse);
QDialogButtonBox *btns = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addRow(btns);
connect(btns, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
connect(btns, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
if(dlg.exec() == QDialog::Accepted) {
performFit(xData, yData, type->currentIndex(), order->value(), inverse->isChecked());
}
}
selectionRect->setVisible(false);
plot->replot();
}
});
configureRawPlot();
configureRawPlot();
}
void GraphWidget::scaleX() { plot->rescaleAxes(true); plot->replot(); }
void GraphWidget::scaleY() { plot->yAxis->rescale(true); plot->yAxis2->rescale(true); plot->replot(); }
void GraphWidget::scaleBoth() { plot->rescaleAxes(true); plot->replot(); }
void GraphWidget::scaleX() {
plot->rescaleAxes(true);
plot->replot();
}
void GraphWidget::scaleY() {
plot->yAxis->rescale(true);
plot->yAxis2->rescale(true);
plot->replot();
}
void GraphWidget::scaleBoth() {
plot->rescaleAxes(true);
plot->replot();
}
void GraphWidget::centerView() { scaleBoth(); }
void GraphWidget::startAnalyze() {
isSelecting = true;
// FIX: Use default constructor for empty flags instead of 0
plot->setInteractions(QCP::Interactions());
isSelecting = true;
// FIX: Use default constructor for empty flags instead of 0
plot->setInteractions(QCP::Interactions());
}
void GraphWidget::performFit(const QVector<double>& x, const QVector<double>& y, int type, int order, bool inverse) {
graphFit->data()->clear();
QVector<double> xFit, yFit;
void GraphWidget::performFit(const QVector<double> &x, const QVector<double> &y,
int type, int order, bool inverse) {
graphFit->data()->clear();
QVector<double> xFit, yFit;
// Simple Linear Fit Implementation for demo
if(type == 0) { // Linear
double m, c;
QVector<double> yProc = y;
if(inverse) {
for(int i=0; i<y.size(); i++) yProc[i] = 1.0/y[i];
}
linearRegression(x, yProc, m, c);
double minX = *std::min_element(x.begin(), x.end());
double maxX = *std::max_element(x.begin(), x.end());
for(int i=0; i<=100; i++) {
double xv = minX + (maxX-minX)*i/100.0;
double yv = m*xv + c;
if(inverse) yv = 1.0/yv;
xFit.append(xv);
yFit.append(yv);
}
// Simple Linear Fit Implementation for demo
if (type == 0) { // Linear
double m, c;
QVector<double> yProc = y;
if (inverse) {
for (int i = 0; i < y.size(); i++)
yProc[i] = 1.0 / y[i];
}
// Add other fits here (Exp, Log, Poly) as needed
linearRegression(x, yProc, m, c);
graphFit->setData(xFit, yFit);
graphFit->setVisible(true);
plot->replot();
double minX = *std::min_element(x.begin(), x.end());
double maxX = *std::max_element(x.begin(), x.end());
for (int i = 0; i <= 100; i++) {
double xv = minX + (maxX - minX) * i / 100.0;
double yv = m * xv + c;
if (inverse)
yv = 1.0 / yv;
xFit.append(xv);
yFit.append(yv);
}
}
// Add other fits here (Exp, Log, Poly) as needed
graphFit->setData(xFit, yFit);
graphFit->setVisible(true);
plot->replot();
}
// ... (Rest of configuration methods same as before) ...
void GraphWidget::configureRawPlot() {
plot->xAxis->setLabel("Frequency (Hz)");
plot->xAxis->setScaleType(QCPAxis::stLogarithmic);
QSharedPointer<QCPAxisTickerLog> logTicker(new QCPAxisTickerLog);
plot->xAxis->setTicker(logTicker);
plot->xAxis->setNumberFormat("eb");
plot->xAxis->setLabel("Frequency (Hz)");
plot->xAxis->setScaleType(QCPAxis::stLogarithmic);
QSharedPointer<QCPAxisTickerLog> logTicker(new QCPAxisTickerLog);
plot->xAxis->setTicker(logTicker);
plot->xAxis->setNumberFormat("eb");
plot->yAxis->setLabel("Real (Ohms)");
plot->yAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
plot->yAxis->setLabel("Magnitude (Ohms)");
plot->yAxis->setScaleType(QCPAxis::stLogarithmic);
QSharedPointer<QCPAxisTickerLog> logTickerY(new QCPAxisTickerLog);
plot->yAxis->setTicker(logTickerY);
plot->yAxis->setNumberFormat("eb");
plot->yAxis2->setLabel("Imaginary (Ohms)");
plot->yAxis2->setScaleType(QCPAxis::stLinear);
plot->yAxis2->setTicker(linTicker);
plot->yAxis2->setNumberFormat("f");
plot->yAxis2->setVisible(true);
plot->yAxis2->setLabel("Phase (Rad)");
plot->yAxis2->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->yAxis2->setTicker(linTicker);
plot->yAxis2->setNumberFormat("f");
plot->yAxis2->setVisible(true);
graphReal->setName("Real");
graphImag->setName("Imaginary");
graphHilbert->setName("Hilbert");
graphReal->setName("Magnitude");
graphImag->setName("Phase");
graphHilbert->setName("Hilbert");
graphReal->setVisible(true);
graphImag->setVisible(true);
graphHilbert->setVisible(true);
graphNyquistCorr->setVisible(false);
graphAmp->setVisible(false);
graphExtrapolated->setVisible(false);
graphLSVBlank->setVisible(false);
graphLSVSample->setVisible(false);
graphLSVDiff->setVisible(false);
graphFit->setVisible(false);
graphReal->setVisible(true);
graphImag->setVisible(true);
graphHilbert->setVisible(true);
graphNyquistCorr->setVisible(false);
graphAmp->setVisible(false);
graphExtrapolated->setVisible(false);
graphLSVBlank->setVisible(false);
graphLSVSample->setVisible(false);
graphLSVDiff->setVisible(false);
graphFit->setVisible(false);
plot->replot();
plot->replot();
}
void GraphWidget::configureNyquistPlot() {
plot->xAxis->setLabel("Real (Z')");
plot->xAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->xAxis->setTicker(linTicker);
plot->xAxis->setNumberFormat("f");
plot->xAxis->setLabel("Real (Z')");
plot->xAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->xAxis->setTicker(linTicker);
plot->xAxis->setNumberFormat("f");
plot->yAxis->setLabel("-Imaginary (-Z'')");
plot->yAxis->setScaleType(QCPAxis::stLinear);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
plot->yAxis->setLabel("-Imaginary (-Z'')");
plot->yAxis->setScaleType(QCPAxis::stLinear);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
plot->yAxis2->setVisible(false);
plot->yAxis2->setVisible(false);
graphReal->setName("Measured (Raw)");
graphReal->setLineStyle(QCPGraph::lsLine);
graphReal->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 4));
graphReal->setName("Measured (Raw)");
graphReal->setLineStyle(QCPGraph::lsLine);
graphReal->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 4));
graphReal->setVisible(true);
graphImag->setVisible(false);
graphHilbert->setVisible(false);
graphNyquistCorr->setVisible(true);
graphAmp->setVisible(false);
graphExtrapolated->setVisible(true);
graphLSVBlank->setVisible(false);
graphLSVSample->setVisible(false);
graphLSVDiff->setVisible(false);
graphFit->setVisible(false);
graphReal->setVisible(true);
graphImag->setVisible(false);
graphHilbert->setVisible(false);
graphNyquistCorr->setVisible(true);
graphAmp->setVisible(false);
graphExtrapolated->setVisible(true);
graphLSVBlank->setVisible(false);
graphLSVSample->setVisible(false);
graphLSVDiff->setVisible(false);
graphFit->setVisible(false);
plot->replot();
plot->replot();
}
void GraphWidget::configureAmperometricPlot() {
plot->xAxis->setLabel("Sample Index");
plot->xAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->xAxis->setTicker(linTicker);
plot->xAxis->setNumberFormat("f");
plot->xAxis->setLabel("Sample Index");
plot->xAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->xAxis->setTicker(linTicker);
plot->xAxis->setNumberFormat("f");
plot->yAxis->setLabel("Current (uA)");
plot->yAxis->setScaleType(QCPAxis::stLinear);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
plot->yAxis->setLabel("Current (uA)");
plot->yAxis->setScaleType(QCPAxis::stLinear);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
plot->yAxis2->setVisible(false);
plot->yAxis2->setVisible(false);
graphAmp->setName("Current");
graphAmp->setVisible(true);
graphAmp->setName("Current");
graphAmp->setVisible(true);
graphReal->setVisible(false);
graphImag->setVisible(false);
graphHilbert->setVisible(false);
graphNyquistCorr->setVisible(false);
graphExtrapolated->setVisible(false);
graphLSVBlank->setVisible(false);
graphLSVSample->setVisible(false);
graphLSVDiff->setVisible(false);
graphFit->setVisible(false);
graphReal->setVisible(false);
graphImag->setVisible(false);
graphHilbert->setVisible(false);
graphNyquistCorr->setVisible(false);
graphExtrapolated->setVisible(false);
graphLSVBlank->setVisible(false);
graphLSVSample->setVisible(false);
graphLSVDiff->setVisible(false);
graphFit->setVisible(false);
plot->replot();
plot->replot();
}
void GraphWidget::configureLSVPlot() {
plot->xAxis->setLabel("Voltage (mV)");
plot->xAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->xAxis->setTicker(linTicker);
plot->xAxis->setNumberFormat("f");
plot->xAxis->setLabel("Voltage (mV)");
plot->xAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->xAxis->setTicker(linTicker);
plot->xAxis->setNumberFormat("f");
plot->yAxis->setLabel("Current (uA)");
plot->yAxis->setScaleType(QCPAxis::stLinear);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
plot->yAxis->setLabel("Current (uA)");
plot->yAxis->setScaleType(QCPAxis::stLinear);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
plot->yAxis2->setVisible(false);
plot->yAxis2->setVisible(false);
graphLSVBlank->setVisible(true);
graphLSVSample->setVisible(true);
graphLSVDiff->setVisible(true);
graphLSVBlank->setVisible(true);
graphLSVSample->setVisible(true);
graphLSVDiff->setVisible(true);
graphReal->setVisible(false);
graphImag->setVisible(false);
graphHilbert->setVisible(false);
graphNyquistCorr->setVisible(false);
graphExtrapolated->setVisible(false);
graphAmp->setVisible(false);
graphFit->setVisible(false);
graphReal->setVisible(false);
graphImag->setVisible(false);
graphHilbert->setVisible(false);
graphNyquistCorr->setVisible(false);
graphExtrapolated->setVisible(false);
graphAmp->setVisible(false);
graphFit->setVisible(false);
plot->replot();
plot->replot();
}
void GraphWidget::addBodeData(double freq, double val1, double val2) {
if (plot->xAxis->scaleType() == QCPAxis::stLogarithmic && freq <= 0) return;
graphReal->addData(freq, val1);
graphImag->addData(freq, val2);
graphReal->rescaleAxes(false);
graphImag->rescaleAxes(false);
graphHilbert->rescaleAxes(false);
plot->replot();
if (plot->xAxis->scaleType() == QCPAxis::stLogarithmic && freq <= 0)
return;
graphReal->addData(freq, val1);
graphImag->addData(freq, val2);
graphReal->rescaleAxes(false);
graphImag->rescaleAxes(false);
graphHilbert->rescaleAxes(false);
plot->replot();
}
void GraphWidget::addNyquistData(double r_meas, double i_meas, double r_corr, double i_corr, bool showCorr) {
graphNyquistRaw->addData(r_meas, -i_meas);
if (showCorr) {
graphNyquistCorr->addData(r_corr, -i_corr);
}
graphNyquistRaw->rescaleAxes(false);
if (showCorr) {
graphNyquistCorr->rescaleAxes(true);
}
plot->replot();
void GraphWidget::addNyquistData(double r_meas, double i_meas, double r_corr,
double i_corr, bool showCorr) {
graphNyquistRaw->addData(r_meas, -i_meas);
if (showCorr) {
graphNyquistCorr->addData(r_corr, -i_corr);
}
graphNyquistRaw->rescaleAxes(false);
if (showCorr) {
graphNyquistCorr->rescaleAxes(true);
}
plot->replot();
}
void GraphWidget::addAmperometricData(double index, double current) {
graphAmp->addData(index, current);
graphAmp->rescaleAxes(false);
plot->replot();
graphAmp->addData(index, current);
graphAmp->rescaleAxes(false);
plot->replot();
}
void GraphWidget::addLSVData(double voltage, double current, LSVTrace traceType) {
QCPGraph* target = nullptr;
switch(traceType) {
case LSV_BLANK: target = graphLSVBlank; break;
case LSV_SAMPLE: target = graphLSVSample; break;
case LSV_DIFF: target = graphLSVDiff; break;
}
void GraphWidget::addLSVData(double voltage, double current,
LSVTrace traceType) {
QCPGraph *target = nullptr;
switch (traceType) {
case LSV_BLANK:
target = graphLSVBlank;
break;
case LSV_SAMPLE:
target = graphLSVSample;
break;
case LSV_DIFF:
target = graphLSVDiff;
break;
}
if (target) {
target->addData(voltage, current);
target->rescaleAxes(false);
plot->replot();
}
if (target) {
target->addData(voltage, current);
target->rescaleAxes(false);
plot->replot();
}
}
void GraphWidget::addHilbertData(const QVector<double>& freq, const QVector<double>& hilbertImag) {
if (plot->xAxis->label() != "Frequency (Hz)") return;
graphHilbert->setData(freq, hilbertImag);
graphHilbert->rescaleAxes(false);
plot->replot();
void GraphWidget::addHilbertData(const QVector<double> &freq,
const QVector<double> &hilbertImag) {
if (plot->xAxis->label() != "Frequency (Hz)")
return;
graphHilbert->setData(freq, hilbertImag);
graphHilbert->rescaleAxes(false);
plot->replot();
}
void GraphWidget::setExtrapolatedPoint(double real, double imag) {
graphExtrapolated->data()->clear();
graphExtrapolated->addData(real, -imag);
plot->replot();
graphExtrapolated->data()->clear();
graphExtrapolated->addData(real, -imag);
plot->replot();
}
void GraphWidget::clear() {
graphReal->data()->clear();
graphImag->data()->clear();
graphHilbert->data()->clear();
graphNyquistCorr->data()->clear();
graphAmp->data()->clear();
graphExtrapolated->data()->clear();
graphFit->data()->clear();
plot->replot();
graphReal->data()->clear();
graphImag->data()->clear();
graphHilbert->data()->clear();
graphNyquistCorr->data()->clear();
graphAmp->data()->clear();
graphExtrapolated->data()->clear();
graphFit->data()->clear();
plot->replot();
}
void GraphWidget::clearLSV(LSVTrace traceType) {
if (traceType == LSV_BLANK) graphLSVBlank->data()->clear();
if (traceType == LSV_SAMPLE) graphLSVSample->data()->clear();
if (traceType == LSV_DIFF) graphLSVDiff->data()->clear();
plot->replot();
if (traceType == LSV_BLANK)
graphLSVBlank->data()->clear();
if (traceType == LSV_SAMPLE)
graphLSVSample->data()->clear();
if (traceType == LSV_DIFF)
graphLSVDiff->data()->clear();
plot->replot();
}

View File

@ -1,151 +1,153 @@
// File: host/src/MainWindow.h
#pragma once
#include <QMainWindow>
#include <QSerialPort>
#include <QTabWidget>
#include <QTextEdit>
#include <QToolBar>
#include "GraphWidget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QPushButton>
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QEvent>
#include <QGestureEvent>
#include <QSwipeGesture>
#include <QTimer>
#include <QCheckBox>
#include <QSettings>
#include <QGroupBox>
#include "GraphWidget.h"
#include <QMainWindow>
#include <QMessageBox>
#include <QPushButton>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QSettings>
#include <QSwipeGesture>
#include <QTabWidget>
#include <QTextEdit>
#include <QTimer>
#include <QToolBar>
class MainWindow : public QMainWindow {
Q_OBJECT
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
bool event(QEvent *event) override;
bool event(QEvent *event) override;
private slots:
// Serial Slots
void handleSerialData();
void connectToPort();
void refreshPorts();
void onPortError(QSerialPort::SerialPortError error);
// Serial Slots
void handleSerialData();
void connectToPort();
void refreshPorts();
void onPortError(QSerialPort::SerialPortError error);
// UI Slots
void onBlinkTimer();
void onLPFChanged(int index);
void onRLoadChanged(int index); // New Slot
void onShortRe0Se0Toggled(bool checked);
// UI Slots
void onBlinkTimer();
void onLPFChanged(int index);
void onRLoadChanged(int index); // New Slot
// Action Slots
void checkDeviceId();
void runCalibration();
void startSweep();
void toggleMeasurement();
void toggleAmperometry();
// Action Slots
void checkDeviceId();
void runCalibration();
void startSweep();
void toggleMeasurement();
void toggleAmperometry();
// LSV Slots
void startLSVBlank();
void startLSVSample();
void stopLSV();
// LSV Slots
void startLSVBlank();
void startLSVSample();
void stopLSV();
void calibrateCellConstant();
void calibrateCellConstant();
private:
// Split Implementation Methods
void setupUi();
void loadSettings();
void saveSettings();
void parseData(const QString &data);
void handleSwipe(QSwipeGesture *gesture);
void computeHilbert();
void performCircleFit();
void calculateLSVDiff();
void setButtonBlinking(QPushButton *btn, bool blinking);
// Split Implementation Methods
void setupUi();
void loadSettings();
void saveSettings();
void parseData(const QString &data);
void handleSwipe(QSwipeGesture *gesture);
void computeHilbert();
void performCircleFit();
void calculateLSVDiff();
void setButtonBlinking(QPushButton *btn, bool blinking);
// Sequence Handling
enum SequenceState { SEQ_IDLE, SEQ_WAIT_BOOT, SEQ_WAIT_CALIB };
SequenceState currentSequence = SEQ_IDLE;
QString pendingCommand;
void initiateSequence(const QString &cmd);
// Sequence Handling
enum SequenceState { SEQ_IDLE, SEQ_WAIT_BOOT, SEQ_WAIT_CALIB };
SequenceState currentSequence = SEQ_IDLE;
QString pendingCommand;
void initiateSequence(const QString &cmd);
QSerialPort *serial;
QSettings *settings;
QTimer *blinkTimer;
QPushButton *activeButton = nullptr;
bool blinkState = false;
QSerialPort *serial;
QSettings *settings;
QTimer *blinkTimer;
QPushButton *activeButton = nullptr;
bool blinkState = false;
// Views
GraphWidget *rawGraph;
GraphWidget *nyquistGraph;
GraphWidget *ampGraph;
GraphWidget *lsvGraph;
QTextEdit *logWidget;
// Views
GraphWidget *rawGraph;
GraphWidget *nyquistGraph;
GraphWidget *ampGraph;
GraphWidget *lsvGraph;
QTextEdit *logWidget;
// Layout Containers
QTabWidget *mainTabWidget;
QTabWidget *impGraphTabs;
// Layout Containers
QTabWidget *mainTabWidget;
QTabWidget *impGraphTabs;
// --- Global Controls ---
QComboBox *portSelector;
QPushButton *connectBtn;
QPushButton *checkIdBtn;
QPushButton *calibrateBtn;
QComboBox *comboRangeLP;
QComboBox *comboRangeHP;
QComboBox *comboRLoad; // New Control
QCheckBox *checkShortRe0Se0;
// --- Global Controls ---
QComboBox *portSelector;
QPushButton *connectBtn;
QPushButton *checkIdBtn;
QPushButton *calibrateBtn;
QComboBox *comboRangeLP;
QComboBox *comboRangeHP;
QComboBox *comboRLoad; // New Control
// Removed: checkShortRe0Se0
// --- Impedance Controls ---
QDoubleSpinBox *spinSweepStart;
QDoubleSpinBox *spinSweepStop;
QSpinBox *spinSweepPPD;
QPushButton *sweepBtn;
// --- Impedance Controls ---
QDoubleSpinBox *spinSweepStart;
QDoubleSpinBox *spinSweepStop;
QSpinBox *spinSweepPPD;
QPushButton *sweepBtn;
QDoubleSpinBox *spinFreq;
QPushButton *measureBtn;
QDoubleSpinBox *spinFreq;
QDoubleSpinBox *spinImpBias; // New: Impedance DC Bias
QPushButton *measureBtn;
QCheckBox *checkShunt;
QDoubleSpinBox *spinShuntRes;
// Removed: checkShunt, spinShuntRes
QDoubleSpinBox *spinCondStd;
QPushButton *btnCalCond;
QLabel *lblResultRs;
QLabel *lblResultCond;
QDoubleSpinBox *spinCondStd;
QPushButton *btnCalCond;
QLabel *lblResultRs;
QLabel *lblResultCond;
// --- Amperometry Controls ---
QDoubleSpinBox *spinAmpBias;
QPushButton *ampBtn;
QComboBox *comboLPF;
// --- Amperometry Controls ---
QDoubleSpinBox *spinAmpBias;
QPushButton *ampBtn;
QComboBox *comboLPF;
// --- LSV Controls ---
QDoubleSpinBox *spinLsvStart;
QDoubleSpinBox *spinLsvStop;
QSpinBox *spinLsvSteps;
QSpinBox *spinLsvDuration;
QPushButton *lsvBlankBtn;
QPushButton *lsvSampleBtn;
// --- LSV Controls ---
QDoubleSpinBox *spinLsvStart;
QDoubleSpinBox *spinLsvStop;
QSpinBox *spinLsvSteps;
QSpinBox *spinLsvDuration;
QPushButton *lsvBlankBtn;
QPushButton *lsvSampleBtn;
// State
double cellConstant = 1.0;
bool isMeasuringImp = false;
bool isMeasuringAmp = false;
bool isSweeping = false;
// State
double cellConstant = 1.0;
bool isMeasuringImp = false;
bool isMeasuringAmp = false;
bool isSweeping = false;
enum LSVState { LSV_IDLE, LSV_RUNNING_BLANK, LSV_RUNNING_SAMPLE };
LSVState lsvState = LSV_IDLE;
enum LSVState { LSV_IDLE, LSV_RUNNING_BLANK, LSV_RUNNING_SAMPLE };
LSVState lsvState = LSV_IDLE;
// Data Accumulation
QVector<double> sweepFreqs;
QVector<double> sweepReals;
QVector<double> sweepImags;
// Data Accumulation
QVector<double> sweepFreqs;
QVector<double> sweepReals;
QVector<double> sweepImags;
struct LSVPoint { double voltage; double current; };
QVector<LSVPoint> lsvBlankData;
QVector<LSVPoint> lsvSampleData;
struct LSVPoint {
double voltage;
double current;
};
QVector<LSVPoint> lsvBlankData;
QVector<LSVPoint> lsvSampleData;
};

View File

@ -1,394 +1,435 @@
// File: host/src/MainWindow_Actions.cpp
#include "MainWindow.h"
#include <complex>
#include <vector>
#include <cmath>
#include <complex>
#include <fftw3.h>
#include <vector>
void MainWindow::checkDeviceId() {
if (serial->isOpen()) serial->write("v\n");
if (serial->isOpen())
serial->write("v\n");
}
void MainWindow::runCalibration() {
if (serial->isOpen()) {
int lpVal = comboRangeLP->currentData().toInt();
int hpVal = comboRangeHP->currentData().toInt();
serial->write(QString("r %1 %2\n").arg(lpVal).arg(hpVal).toUtf8());
serial->write("c\n");
}
if (serial->isOpen()) {
int lpVal = comboRangeLP->currentData().toInt();
int hpVal = comboRangeHP->currentData().toInt();
serial->write(QString("r %1 %2\n").arg(lpVal).arg(hpVal).toUtf8());
serial->write("c\n");
}
}
void MainWindow::onLPFChanged(int index) {
if (serial->isOpen()) {
int val = comboLPF->itemData(index).toInt();
serial->write(QString("f %1\n").arg(val).toUtf8());
}
if (serial->isOpen()) {
int val = comboLPF->itemData(index).toInt();
serial->write(QString("f %1\n").arg(val).toUtf8());
}
}
void MainWindow::onRLoadChanged(int index) {
if (serial->isOpen()) {
int val = comboRLoad->itemData(index).toInt();
serial->write(QString("L %1\n").arg(val).toUtf8());
}
if (serial->isOpen()) {
int val = comboRLoad->itemData(index).toInt();
serial->write(QString("L %1\n").arg(val).toUtf8());
}
}
void MainWindow::onShortRe0Se0Toggled(bool checked) {
if (serial->isOpen()) {
serial->write(QString("t %1\n").arg(checked ? 1 : 0).toUtf8());
logWidget->append(QString(">> RE0-SE0 Short: %1").arg(checked ? "ON" : "OFF"));
}
}
// Removed: onShortRe0Se0Toggled
void MainWindow::initiateSequence(const QString &cmd) {
if (!serial->isOpen()) return;
if (!serial->isOpen())
return;
pendingCommand = cmd;
currentSequence = SEQ_WAIT_BOOT;
pendingCommand = cmd;
currentSequence = SEQ_WAIT_BOOT;
logWidget->append(">> Sequence: Resetting device (z)...");
serial->write("z\n"); // Watchdog reboot to force clean state
logWidget->append(">> Sequence: Resetting device (z)...");
serial->write("z\n"); // Watchdog reboot to force clean state
}
void MainWindow::startSweep() {
if (!serial->isOpen()) return;
if (!serial->isOpen())
return;
if (isSweeping) {
// Stop Sweep
serial->write("x\n");
return;
}
if (isSweeping) {
// Stop Sweep
serial->write("x\n");
return;
}
if (isMeasuringAmp) toggleAmperometry();
if (lsvState != LSV_IDLE) stopLSV();
if (isMeasuringImp) toggleMeasurement();
if (isMeasuringAmp)
toggleAmperometry();
if (lsvState != LSV_IDLE)
stopLSV();
if (isMeasuringImp)
toggleMeasurement();
rawGraph->clear();
nyquistGraph->clear();
rawGraph->clear();
nyquistGraph->clear();
sweepFreqs.clear();
sweepReals.clear();
sweepImags.clear();
sweepFreqs.clear();
sweepReals.clear();
sweepImags.clear();
double start = spinSweepStart->value();
double stop = spinSweepStop->value();
int ppd = spinSweepPPD->value();
double start = spinSweepStart->value();
double stop = spinSweepStop->value();
int ppd = spinSweepPPD->value();
double bias = spinImpBias->value(); // Use the Imp Bias control
QString cmd = QString("s %1 %2 %3").arg(start).arg(stop).arg(ppd);
QString cmd =
QString("s %1 %2 %3 %4").arg(start).arg(stop).arg(ppd).arg(bias);
isSweeping = true;
sweepBtn->setText("Stop Sweep");
setButtonBlinking(sweepBtn, true);
isSweeping = true;
sweepBtn->setText("Stop Sweep");
setButtonBlinking(sweepBtn, true);
initiateSequence(cmd);
initiateSequence(cmd);
}
void MainWindow::toggleMeasurement() {
if (!serial->isOpen()) return;
if (isMeasuringAmp) toggleAmperometry();
if (lsvState != LSV_IDLE) stopLSV();
if (isSweeping) startSweep(); // Stop sweep
if (!serial->isOpen())
return;
if (isMeasuringAmp)
toggleAmperometry();
if (lsvState != LSV_IDLE)
stopLSV();
if (isSweeping)
startSweep(); // Stop sweep
if (isMeasuringImp) {
serial->write("x\n");
measureBtn->setText("Measure");
setButtonBlinking(nullptr, false);
isMeasuringImp = false;
} else {
double freq = spinFreq->value();
QString cmd = QString("m %1").arg(freq);
if (isMeasuringImp) {
serial->write("x\n");
measureBtn->setText("Measure");
setButtonBlinking(nullptr, false);
isMeasuringImp = false;
} else {
double freq = spinFreq->value();
double bias = spinImpBias->value();
QString cmd = QString("m %1 %2").arg(freq).arg(bias);
measureBtn->setText("Stop");
setButtonBlinking(measureBtn, true);
isMeasuringImp = true;
measureBtn->setText("Stop");
setButtonBlinking(measureBtn, true);
isMeasuringImp = true;
initiateSequence(cmd);
}
initiateSequence(cmd);
}
}
void MainWindow::toggleAmperometry() {
if (!serial->isOpen()) return;
if (isMeasuringImp) toggleMeasurement();
if (lsvState != LSV_IDLE) stopLSV();
if (isSweeping) startSweep();
if (!serial->isOpen())
return;
if (isMeasuringImp)
toggleMeasurement();
if (lsvState != LSV_IDLE)
stopLSV();
if (isSweeping)
startSweep();
if (isMeasuringAmp) {
serial->write("x\n");
ampBtn->setText("Start Amp");
setButtonBlinking(nullptr, false);
isMeasuringAmp = false;
} else {
double bias = spinAmpBias->value();
QString cmd = QString("a %1").arg(bias);
if (isMeasuringAmp) {
serial->write("x\n");
ampBtn->setText("Start Amp");
setButtonBlinking(nullptr, false);
isMeasuringAmp = false;
} else {
double bias = spinAmpBias->value();
QString cmd = QString("a %1").arg(bias);
ampBtn->setText("Stop Amp");
setButtonBlinking(ampBtn, true);
isMeasuringAmp = true;
ampGraph->clear();
ampBtn->setText("Stop Amp");
setButtonBlinking(ampBtn, true);
isMeasuringAmp = true;
ampGraph->clear();
initiateSequence(cmd);
}
initiateSequence(cmd);
}
}
void MainWindow::startLSVBlank() {
if (!serial->isOpen()) return;
if (!serial->isOpen())
return;
// Toggle Logic: If running, stop it.
if (lsvState == LSV_RUNNING_BLANK) {
stopLSV();
return;
}
// Toggle Logic: If running, stop it.
if (lsvState == LSV_RUNNING_BLANK) {
stopLSV();
return;
}
if (isMeasuringImp) toggleMeasurement();
if (isMeasuringAmp) toggleAmperometry();
if (lsvState != LSV_IDLE) stopLSV(); // Stop sample if running
if (isSweeping) startSweep();
if (isMeasuringImp)
toggleMeasurement();
if (isMeasuringAmp)
toggleAmperometry();
if (lsvState != LSV_IDLE)
stopLSV(); // Stop sample if running
if (isSweeping)
startSweep();
double start = spinLsvStart->value();
double stop = spinLsvStop->value();
int steps = spinLsvSteps->value();
int duration = spinLsvDuration->value();
double start = spinLsvStart->value();
double stop = spinLsvStop->value();
int steps = spinLsvSteps->value();
int duration = spinLsvDuration->value();
QString cmd = QString("l %1 %2 %3 %4").arg(start).arg(stop).arg(steps).arg(duration);
QString cmd =
QString("l %1 %2 %3 %4").arg(start).arg(stop).arg(steps).arg(duration);
lsvBlankBtn->setText("Stop Blank");
setButtonBlinking(lsvBlankBtn, true);
lsvState = LSV_RUNNING_BLANK;
lsvBlankBtn->setText("Stop Blank");
setButtonBlinking(lsvBlankBtn, true);
lsvState = LSV_RUNNING_BLANK;
// Only clear if we are actually starting a new run
lsvGraph->clearLSV(GraphWidget::LSV_BLANK);
lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
lsvBlankData.clear();
// Only clear if we are actually starting a new run
lsvGraph->clearLSV(GraphWidget::LSV_BLANK);
lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
lsvBlankData.clear();
initiateSequence(cmd);
initiateSequence(cmd);
}
void MainWindow::startLSVSample() {
if (!serial->isOpen()) return;
if (!serial->isOpen())
return;
// Toggle Logic
if (lsvState == LSV_RUNNING_SAMPLE) {
stopLSV();
return;
}
// Toggle Logic
if (lsvState == LSV_RUNNING_SAMPLE) {
stopLSV();
return;
}
if (isMeasuringImp) toggleMeasurement();
if (isMeasuringAmp) toggleAmperometry();
if (lsvState != LSV_IDLE) stopLSV(); // Stop blank if running
if (isSweeping) startSweep();
if (isMeasuringImp)
toggleMeasurement();
if (isMeasuringAmp)
toggleAmperometry();
if (lsvState != LSV_IDLE)
stopLSV(); // Stop blank if running
if (isSweeping)
startSweep();
double start = spinLsvStart->value();
double stop = spinLsvStop->value();
int steps = spinLsvSteps->value();
int duration = spinLsvDuration->value();
double start = spinLsvStart->value();
double stop = spinLsvStop->value();
int steps = spinLsvSteps->value();
int duration = spinLsvDuration->value();
QString cmd = QString("l %1 %2 %3 %4").arg(start).arg(stop).arg(steps).arg(duration);
QString cmd =
QString("l %1 %2 %3 %4").arg(start).arg(stop).arg(steps).arg(duration);
lsvSampleBtn->setText("Stop Sample");
setButtonBlinking(lsvSampleBtn, true);
lsvState = LSV_RUNNING_SAMPLE;
lsvSampleBtn->setText("Stop Sample");
setButtonBlinking(lsvSampleBtn, true);
lsvState = LSV_RUNNING_SAMPLE;
// Only clear if we are actually starting a new run
lsvGraph->clearLSV(GraphWidget::LSV_SAMPLE);
lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
lsvSampleData.clear();
// Only clear if we are actually starting a new run
lsvGraph->clearLSV(GraphWidget::LSV_SAMPLE);
lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
lsvSampleData.clear();
initiateSequence(cmd);
initiateSequence(cmd);
}
void MainWindow::stopLSV() {
serial->write("x\n");
lsvBlankBtn->setText("Run Blank");
lsvSampleBtn->setText("Run Sample");
setButtonBlinking(nullptr, false);
serial->write("x\n");
lsvBlankBtn->setText("Run Blank");
lsvSampleBtn->setText("Run Sample");
setButtonBlinking(nullptr, false);
// If we just finished a sample run, calculate the difference
if (lsvState == LSV_RUNNING_SAMPLE) {
calculateLSVDiff();
}
// If we just finished a sample run, calculate the difference
if (lsvState == LSV_RUNNING_SAMPLE) {
calculateLSVDiff();
}
lsvState = LSV_IDLE;
lsvState = LSV_IDLE;
}
void MainWindow::calculateLSVDiff() {
if (lsvBlankData.isEmpty() || lsvSampleData.isEmpty()) return;
if (lsvBlankData.isEmpty() || lsvSampleData.isEmpty())
return;
lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
// Simple index-based subtraction (assumes same parameters used)
int count = std::min(lsvBlankData.size(), lsvSampleData.size());
// Simple index-based subtraction (assumes same parameters used)
int count = std::min(lsvBlankData.size(), lsvSampleData.size());
for (int i = 0; i < count; i++) {
double v = lsvSampleData[i].voltage; // Use sample voltage
double diffCurrent = lsvSampleData[i].current - lsvBlankData[i].current;
lsvGraph->addLSVData(v, diffCurrent, GraphWidget::LSV_DIFF);
}
for (int i = 0; i < count; i++) {
double v = lsvSampleData[i].voltage; // Use sample voltage
double diffCurrent = lsvSampleData[i].current - lsvBlankData[i].current;
lsvGraph->addLSVData(v, diffCurrent, GraphWidget::LSV_DIFF);
}
logWidget->append(">> Calculated Difference Curve.");
logWidget->append(">> Calculated Difference Curve.");
}
void MainWindow::computeHilbert() {
int n = sweepReals.size();
if (n == 0) return;
int n = sweepReals.size();
if (n == 0)
return;
// Pad to next power of 2 for efficiency and consistency with previous logic
int n_fft = 1;
while (n_fft < n) n_fft *= 2;
// Pad to next power of 2 for efficiency and consistency with previous logic
int n_fft = 1;
while (n_fft < n)
n_fft *= 2;
// Allocate FFTW arrays
fftw_complex *in = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * n_fft);
fftw_complex *out = (fftw_complex*) fftw_malloc(sizeof(fftw_complex) * n_fft);
// Allocate FFTW arrays
fftw_complex *in = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * n_fft);
fftw_complex *out = (fftw_complex *)fftw_malloc(sizeof(fftw_complex) * n_fft);
// Create Plans (ESTIMATE is fast for one-off sizes)
fftw_plan p_fwd = fftw_plan_dft_1d(n_fft, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
fftw_plan p_bwd = fftw_plan_dft_1d(n_fft, out, in, FFTW_BACKWARD, FFTW_ESTIMATE);
// Create Plans (ESTIMATE is fast for one-off sizes)
fftw_plan p_fwd =
fftw_plan_dft_1d(n_fft, in, out, FFTW_FORWARD, FFTW_ESTIMATE);
fftw_plan p_bwd =
fftw_plan_dft_1d(n_fft, out, in, FFTW_BACKWARD, FFTW_ESTIMATE);
// Prepare Input: Copy data and zero-pad
for (int i = 0; i < n; i++) {
in[i][0] = sweepReals[i]; // Real part
in[i][1] = 0.0; // Imag part
}
for (int i = n; i < n_fft; i++) {
in[i][0] = 0.0;
in[i][1] = 0.0;
}
// Prepare Input: Copy data and zero-pad
for (int i = 0; i < n; i++) {
in[i][0] = sweepReals[i]; // Real part
in[i][1] = 0.0; // Imag part
}
for (int i = n; i < n_fft; i++) {
in[i][0] = 0.0;
in[i][1] = 0.0;
}
// Forward FFT
fftw_execute(p_fwd);
// Forward FFT
fftw_execute(p_fwd);
// Apply Hilbert Mask in Frequency Domain (Analytic Signal)
// H[0] (DC) and H[N/2] (Nyquist) are left alone.
// Positive Frequencies (1 to N/2 - 1) multiplied by 2.
// Negative Frequencies (N/2 + 1 to N - 1) zeroed out.
// Apply Hilbert Mask in Frequency Domain (Analytic Signal)
// H[0] (DC) and H[N/2] (Nyquist) are left alone.
// Positive Frequencies (1 to N/2 - 1) multiplied by 2.
// Negative Frequencies (N/2 + 1 to N - 1) zeroed out.
int half_n = n_fft / 2;
int half_n = n_fft / 2;
// Multiply positive frequencies by 2
for (int i = 1; i < half_n; i++) {
out[i][0] *= 2.0;
out[i][1] *= 2.0;
}
// Multiply positive frequencies by 2
for (int i = 1; i < half_n; i++) {
out[i][0] *= 2.0;
out[i][1] *= 2.0;
}
// Zero out negative frequencies
for (int i = half_n + 1; i < n_fft; i++) {
out[i][0] = 0.0;
out[i][1] = 0.0;
}
// Zero out negative frequencies
for (int i = half_n + 1; i < n_fft; i++) {
out[i][0] = 0.0;
out[i][1] = 0.0;
}
// Inverse FFT
fftw_execute(p_bwd);
// Inverse FFT
fftw_execute(p_bwd);
// Extract Imaginary part of Analytic Signal (Hilbert Transform)
// Note: FFTW IFFT is unnormalized, divide by N
QVector<double> hilbertImag;
hilbertImag.reserve(n);
// Extract Imaginary part of Analytic Signal (Hilbert Transform)
// Note: FFTW IFFT is unnormalized, divide by N
QVector<double> hilbertImag;
hilbertImag.reserve(n);
for (int i = 0; i < n; i++) {
double val = in[i][1] / n_fft; // Imaginary part normalized
hilbertImag.append(val);
}
for (int i = 0; i < n; i++) {
double val = in[i][1] / n_fft; // Imaginary part normalized
hilbertImag.append(val);
}
rawGraph->addHilbertData(sweepFreqs, hilbertImag);
rawGraph->addHilbertData(sweepFreqs, hilbertImag);
// Cleanup
fftw_destroy_plan(p_fwd);
fftw_destroy_plan(p_bwd);
fftw_free(in);
fftw_free(out);
// Cleanup
fftw_destroy_plan(p_fwd);
fftw_destroy_plan(p_bwd);
fftw_free(in);
fftw_free(out);
}
void MainWindow::performCircleFit() {
int n = sweepReals.size();
if (n < 5) return;
int n = sweepReals.size();
if (n < 5)
return;
// 1. Centering (Crucial for stability)
double meanX = 0, meanY = 0;
for(int i=0; i<n; i++) { meanX += sweepReals[i]; meanY += -sweepImags[i]; }
meanX /= n; meanY /= n;
// 1. Centering (Crucial for stability)
double meanX = 0, meanY = 0;
for (int i = 0; i < n; i++) {
meanX += sweepReals[i];
meanY += -sweepImags[i];
}
meanX /= n;
meanY /= n;
// 2. Kasa Fit (Algebraic) on Centered Data
// Minimizes sum((x^2 + y^2) - (2Ax + 2By + C))^2
// 2. Kasa Fit (Algebraic) on Centered Data
// Minimizes sum((x^2 + y^2) - (2Ax + 2By + C))^2
double sum_x2 = 0, sum_y2 = 0, sum_xy = 0;
double sum_z = 0, sum_zx = 0, sum_zy = 0;
double sum_x2 = 0, sum_y2 = 0, sum_xy = 0;
double sum_z = 0, sum_zx = 0, sum_zy = 0;
for(int i=0; i<n; i++) {
double xi = sweepReals[i] - meanX;
double yi = -sweepImags[i] - meanY;
double zi = xi*xi + yi*yi;
for (int i = 0; i < n; i++) {
double xi = sweepReals[i] - meanX;
double yi = -sweepImags[i] - meanY;
double zi = xi * xi + yi * yi;
sum_x2 += xi*xi;
sum_y2 += yi*yi;
sum_xy += xi*yi;
sum_z += zi;
sum_zx += zi*xi;
sum_zy += zi*yi;
}
sum_x2 += xi * xi;
sum_y2 += yi * yi;
sum_xy += xi * yi;
sum_z += zi;
sum_zx += zi * xi;
sum_zy += zi * yi;
}
// Solve 3x3 Linear System (Normal Equations) for Centered Kasa
// [ 4*sum_x2 4*sum_xy 0 ] [ A ] [ 2*sum_zx ]
// [ 4*sum_xy 4*sum_y2 0 ] [ B ] = [ 2*sum_zy ]
// [ 0 0 n ] [ C ] [ sum_z ]
// Solve 3x3 Linear System (Normal Equations) for Centered Kasa
// [ 4*sum_x2 4*sum_xy 0 ] [ A ] [ 2*sum_zx ]
// [ 4*sum_xy 4*sum_y2 0 ] [ B ] = [ 2*sum_zy ]
// [ 0 0 n ] [ C ] [ sum_z ]
double C = sum_z / n;
double C = sum_z / n;
// Solve 2x2 for A, B
double D = 16 * (sum_x2 * sum_y2 - sum_xy * sum_xy);
// Solve 2x2 for A, B
double D = 16 * (sum_x2 * sum_y2 - sum_xy * sum_xy);
if (std::abs(D) < 1e-9) return; // Collinear or insufficient data
if (std::abs(D) < 1e-9)
return; // Collinear or insufficient data
double A = (2 * sum_zx * 4 * sum_y2 - 2 * sum_zy * 4 * sum_xy) / D;
double B = (4 * sum_x2 * 2 * sum_zy - 4 * sum_xy * 2 * sum_zx) / D;
double A = (2 * sum_zx * 4 * sum_y2 - 2 * sum_zy * 4 * sum_xy) / D;
double B = (4 * sum_x2 * 2 * sum_zy - 4 * sum_xy * 2 * sum_zx) / D;
double xc = A + meanX;
double yc = B + meanY;
double r_sq = A*A + B*B + C;
double xc = A + meanX;
double yc = B + meanY;
double r_sq = A * A + B * B + C;
if (r_sq <= 0) return;
double r = std::sqrt(r_sq);
if (r_sq <= 0)
return;
double r = std::sqrt(r_sq);
// Calculate Intercepts with Real Axis (y=0)
// (x - xc)^2 + (0 - yc)^2 = r^2
// (x - xc)^2 = r^2 - yc^2
// Calculate Intercepts with Real Axis (y=0)
// (x - xc)^2 + (0 - yc)^2 = r^2
// (x - xc)^2 = r^2 - yc^2
double term = r*r - yc*yc;
if (term < 0) return; // Circle doesn't intersect real axis
double term = r * r - yc * yc;
if (term < 0)
return; // Circle doesn't intersect real axis
double x1 = xc - std::sqrt(term);
double x2 = xc + std::sqrt(term);
double x1 = xc - std::sqrt(term);
double x2 = xc + std::sqrt(term);
double Rs = std::min(x1, x2);
if (Rs < 0) Rs = std::max(x1, x2); // If one is negative, take the positive one
double Rs = std::min(x1, x2);
if (Rs < 0)
Rs = std::max(x1, x2); // If one is negative, take the positive one
if (Rs > 0) {
lblResultRs->setText(QString(" Rs: %1 Ω").arg(Rs, 0, 'f', 2));
if (Rs > 0) {
lblResultRs->setText(QString(" Rs: %1 Ω").arg(Rs, 0, 'f', 2));
double cond = (cellConstant / Rs) * 1000000.0;
lblResultCond->setText(QString(" Cond: %1 µS/cm").arg(cond, 0, 'f', 2));
double cond = (cellConstant / Rs) * 1000000.0;
lblResultCond->setText(QString(" Cond: %1 µS/cm").arg(cond, 0, 'f', 2));
nyquistGraph->setExtrapolatedPoint(Rs, 0);
}
nyquistGraph->setExtrapolatedPoint(Rs, 0);
}
}
void MainWindow::calibrateCellConstant() {
QString txt = lblResultRs->text();
if (txt.contains("--")) {
QMessageBox::warning(this, "Calibration Error", "No valid Rs measurement found. Run a sweep first.");
return;
}
QString txt = lblResultRs->text();
if (txt.contains("--")) {
QMessageBox::warning(this, "Calibration Error",
"No valid Rs measurement found. Run a sweep first.");
return;
}
QString numStr = txt.section(':', 1).section(QChar(0x03A9), 0, 0).trimmed();
double measuredRs = numStr.toDouble();
QString numStr = txt.section(':', 1).section(QChar(0x03A9), 0, 0).trimmed();
double measuredRs = numStr.toDouble();
if (measuredRs <= 0) return;
if (measuredRs <= 0)
return;
double stdCond = spinCondStd->value();
double stdCond = spinCondStd->value();
cellConstant = (stdCond * 1e-6) * measuredRs;
cellConstant = (stdCond * 1e-6) * measuredRs;
saveSettings();
saveSettings();
QMessageBox::information(this, "Calibration Success",
QString("Cell Constant (K) calibrated to: %1 cm⁻¹").arg(cellConstant, 0, 'f', 4));
QMessageBox::information(this, "Calibration Success",
QString("Cell Constant (K) calibrated to: %1 cm⁻¹")
.arg(cellConstant, 0, 'f', 4));
performCircleFit();
performCircleFit();
}

View File

@ -4,261 +4,253 @@
#include <complex>
void MainWindow::refreshPorts() {
portSelector->clear();
const auto infos = QSerialPortInfo::availablePorts();
bool foundTarget = false;
QString targetPort;
portSelector->clear();
const auto infos = QSerialPortInfo::availablePorts();
bool foundTarget = false;
QString targetPort;
for (const QSerialPortInfo &info : infos) {
portSelector->addItem(info.portName());
bool isCafe = (info.hasVendorIdentifier() && info.vendorIdentifier() == 0xCAFE);
bool isUsbModem = info.portName().contains("usbmodem", Qt::CaseInsensitive);
if ((isCafe || isUsbModem) && !foundTarget) {
targetPort = info.portName();
foundTarget = true;
logWidget->append(">> Found Target Device: " + targetPort);
}
}
if (foundTarget) {
portSelector->setCurrentText(targetPort);
if (!serial->isOpen()) connectToPort();
for (const QSerialPortInfo &info : infos) {
portSelector->addItem(info.portName());
bool isCafe =
(info.hasVendorIdentifier() && info.vendorIdentifier() == 0xCAFE);
bool isUsbModem = info.portName().contains("usbmodem", Qt::CaseInsensitive);
if ((isCafe || isUsbModem) && !foundTarget) {
targetPort = info.portName();
foundTarget = true;
logWidget->append(">> Found Target Device: " + targetPort);
}
}
if (foundTarget) {
portSelector->setCurrentText(targetPort);
if (!serial->isOpen())
connectToPort();
}
}
void MainWindow::connectToPort() {
if (serial->isOpen()) {
serial->close();
connectBtn->setText("Connect");
logWidget->append("--- Disconnected ---");
if (serial->isOpen()) {
serial->close();
connectBtn->setText("Connect");
logWidget->append("--- Disconnected ---");
// 1. Disable Global Controls
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
comboRangeLP->setEnabled(false);
comboRangeHP->setEnabled(false);
comboRLoad->setEnabled(false);
checkShortRe0Se0->setEnabled(false);
// 1. Disable Global Controls
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
comboRangeLP->setEnabled(false);
comboRangeHP->setEnabled(false);
comboRLoad->setEnabled(false);
// 2. Disable Tabs
mainTabWidget->widget(0)->setEnabled(false);
mainTabWidget->widget(1)->setEnabled(false);
mainTabWidget->widget(2)->setEnabled(false);
// 2. Disable Tabs
mainTabWidget->widget(0)->setEnabled(false);
mainTabWidget->widget(1)->setEnabled(false);
mainTabWidget->widget(2)->setEnabled(false);
isMeasuringImp = false;
isMeasuringAmp = false;
isSweeping = false;
lsvState = LSV_IDLE;
currentSequence = SEQ_IDLE;
setButtonBlinking(nullptr, false);
isMeasuringImp = false;
isMeasuringAmp = false;
isSweeping = false;
lsvState = LSV_IDLE;
currentSequence = SEQ_IDLE;
setButtonBlinking(nullptr, false);
measureBtn->setText("Measure");
ampBtn->setText("Start Amp");
lsvBlankBtn->setText("Run Blank");
lsvSampleBtn->setText("Run Sample");
return;
}
measureBtn->setText("Measure");
ampBtn->setText("Start Amp");
lsvBlankBtn->setText("Run Blank");
lsvSampleBtn->setText("Run Sample");
return;
}
if (portSelector->currentText().isEmpty()) return;
if (portSelector->currentText().isEmpty())
return;
serial->setPortName(portSelector->currentText());
serial->setBaudRate(500000);
serial->setPortName(portSelector->currentText());
serial->setBaudRate(500000);
if (serial->open(QIODevice::ReadWrite)) {
connectBtn->setText("Disconnect");
logWidget->append("--- Connected and Synchronized ---");
if (serial->open(QIODevice::ReadWrite)) {
connectBtn->setText("Disconnect");
logWidget->append("--- Connected and Synchronized ---");
// 1. Enable Global Controls
checkIdBtn->setEnabled(true);
calibrateBtn->setEnabled(true);
comboRangeLP->setEnabled(true);
comboRangeHP->setEnabled(true);
comboRLoad->setEnabled(true);
checkShortRe0Se0->setEnabled(true);
// 1. Enable Global Controls
checkIdBtn->setEnabled(true);
calibrateBtn->setEnabled(true);
comboRangeLP->setEnabled(true);
comboRangeHP->setEnabled(true);
comboRLoad->setEnabled(true);
// 2. Enable Tabs
mainTabWidget->widget(0)->setEnabled(true);
mainTabWidget->widget(1)->setEnabled(true);
mainTabWidget->widget(2)->setEnabled(true);
// 2. Enable Tabs
mainTabWidget->widget(0)->setEnabled(true);
mainTabWidget->widget(1)->setEnabled(true);
mainTabWidget->widget(2)->setEnabled(true);
// Sync LPF and RLoad
onLPFChanged(comboLPF->currentIndex());
onRLoadChanged(comboRLoad->currentIndex());
} else {
logWidget->append(">> Connection Error: " + serial->errorString());
}
// Sync LPF and RLoad
onLPFChanged(comboLPF->currentIndex());
onRLoadChanged(comboRLoad->currentIndex());
} else {
logWidget->append(">> Connection Error: " + serial->errorString());
}
}
void MainWindow::onPortError(QSerialPort::SerialPortError error) {
if (error == QSerialPort::ResourceError) {
logWidget->append(">> Critical Error: Connection Lost.");
serial->close();
connectBtn->setText("Connect");
if (error == QSerialPort::ResourceError) {
logWidget->append(">> Critical Error: Connection Lost.");
serial->close();
connectBtn->setText("Connect");
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
comboRangeLP->setEnabled(false);
comboRangeHP->setEnabled(false);
comboRLoad->setEnabled(false);
checkShortRe0Se0->setEnabled(false);
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
comboRangeLP->setEnabled(false);
comboRangeHP->setEnabled(false);
comboRLoad->setEnabled(false);
mainTabWidget->widget(0)->setEnabled(false);
mainTabWidget->widget(1)->setEnabled(false);
mainTabWidget->widget(2)->setEnabled(false);
mainTabWidget->widget(0)->setEnabled(false);
mainTabWidget->widget(1)->setEnabled(false);
mainTabWidget->widget(2)->setEnabled(false);
setButtonBlinking(nullptr, false);
currentSequence = SEQ_IDLE;
}
setButtonBlinking(nullptr, false);
currentSequence = SEQ_IDLE;
}
}
void MainWindow::handleSerialData() {
while (serial->canReadLine()) {
QByteArray line = serial->readLine();
QString str = QString::fromUtf8(line).trimmed();
if (str.isEmpty()) continue;
while (serial->canReadLine()) {
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);
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
logWidget->append(QString("[%1] %2").arg(timestamp, str));
logWidget->moveCursor(QTextCursor::End);
// --- Sequence State Machine ---
if (currentSequence == SEQ_WAIT_BOOT) {
if (str.contains("AD5940LIB Version:v0.2.1")) {
logWidget->append(">> Sequence: Boot detected. Configuring...");
// --- Sequence State Machine ---
if (currentSequence == SEQ_WAIT_BOOT) {
if (str.contains("AD5940LIB Version:v0.2.1")) {
logWidget->append(">> Sequence: Boot detected. Configuring...");
// 1. Restore Settings
int lpVal = comboRangeLP->currentData().toInt();
int hpVal = comboRangeHP->currentData().toInt();
serial->write(QString("r %1 %2\n").arg(lpVal).arg(hpVal).toUtf8());
// 1. Restore Settings
int lpVal = comboRangeLP->currentData().toInt();
int hpVal = comboRangeHP->currentData().toInt();
serial->write(QString("r %1 %2\n").arg(lpVal).arg(hpVal).toUtf8());
int lpfIdx = comboLPF->currentIndex();
int lpfVal = comboLPF->itemData(lpfIdx).toInt();
serial->write(QString("f %1\n").arg(lpfVal).toUtf8());
int lpfIdx = comboLPF->currentIndex();
int lpfVal = comboLPF->itemData(lpfIdx).toInt();
serial->write(QString("f %1\n").arg(lpfVal).toUtf8());
int rloadIdx = comboRLoad->currentIndex();
int rloadVal = comboRLoad->itemData(rloadIdx).toInt();
serial->write(QString("L %1\n").arg(rloadVal).toUtf8());
int rloadIdx = comboRLoad->currentIndex();
int rloadVal = comboRLoad->itemData(rloadIdx).toInt();
serial->write(QString("L %1\n").arg(rloadVal).toUtf8());
serial->write(QString("t %1\n").arg(checkShortRe0Se0->isChecked() ? 1 : 0).toUtf8());
serial->write(QString("t 0\n").toUtf8());
// 2. Start Calibration
logWidget->append(">> Sequence: Calibrating...");
serial->write("c\n");
// 2. Start Calibration
logWidget->append(">> Sequence: Calibrating...");
serial->write("c\n");
currentSequence = SEQ_WAIT_CALIB;
}
}
else if (currentSequence == SEQ_WAIT_CALIB) {
if (str.contains("Calibrated HSTIA:")) {
logWidget->append(">> Sequence: Calibration Done. Sending Command.");
serial->write(pendingCommand.toUtf8());
serial->write("\n");
currentSequence = SEQ_IDLE;
}
}
// ------------------------------
if (str.startsWith("DATA,")) {
parseData(str);
} else if (str.startsWith("AMP,")) {
parseData(str);
} else if (str.startsWith("RAMP,")) {
parseData(str);
} else if (str == "STOPPED") {
// Reset UI state
if (lsvState != LSV_IDLE) stopLSV();
if (isSweeping) {
isSweeping = false;
sweepBtn->setText("Sweep");
setButtonBlinking(nullptr, false);
}
if (isMeasuringImp) {
isMeasuringImp = false;
measureBtn->setText("Measure");
setButtonBlinking(nullptr, false);
}
if (isMeasuringAmp) {
isMeasuringAmp = false;
ampBtn->setText("Start Amp");
setButtonBlinking(nullptr, false);
}
}
currentSequence = SEQ_WAIT_CALIB;
}
} else if (currentSequence == SEQ_WAIT_CALIB) {
if (str.contains("Calibrated HSTIA:")) {
logWidget->append(">> Sequence: Calibration Done. Sending Command.");
serial->write(pendingCommand.toUtf8());
serial->write("\n");
currentSequence = SEQ_IDLE;
}
}
// ------------------------------
if (str.startsWith("DATA,")) {
parseData(str);
} else if (str.startsWith("AMP,")) {
parseData(str);
} else if (str.startsWith("RAMP,")) {
parseData(str);
} else if (str == "STOPPED") {
// Reset UI state
if (lsvState != LSV_IDLE)
stopLSV();
if (isSweeping) {
isSweeping = false;
sweepBtn->setText("Sweep");
setButtonBlinking(nullptr, false);
}
if (isMeasuringImp) {
isMeasuringImp = false;
measureBtn->setText("Measure");
setButtonBlinking(nullptr, false);
}
if (isMeasuringAmp) {
isMeasuringAmp = false;
ampBtn->setText("Start Amp");
setButtonBlinking(nullptr, false);
}
}
}
}
void MainWindow::parseData(const QString &data) {
QStringList parts = data.split(',');
QStringList parts = data.split(',');
if (parts[0] == "AMP" && parts.size() >= 3) {
bool okIdx, okCurr;
double index = parts[1].toDouble(&okIdx);
double current = parts[2].toDouble(&okCurr);
if (okIdx && okCurr) ampGraph->addAmperometricData(index, current);
return;
if (parts[0] == "AMP" && parts.size() >= 3) {
bool okIdx, okCurr;
double index = parts[1].toDouble(&okIdx);
double current = parts[2].toDouble(&okCurr);
if (okIdx && okCurr)
ampGraph->addAmperometricData(index, current);
return;
}
if (parts[0] == "RAMP" && parts.size() >= 3) {
bool okIdx, okCurr;
double index = parts[1].toDouble(&okIdx);
double current = parts[2].toDouble(&okCurr);
if (okIdx && okCurr) {
// Calculate Voltage based on UI parameters (Host-side calculation)
double start = spinLsvStart->value();
double stop = spinLsvStop->value();
int steps = spinLsvSteps->value();
double voltage = start + (index * (stop - start) / steps);
if (lsvState == LSV_RUNNING_BLANK) {
lsvGraph->addLSVData(voltage, current, GraphWidget::LSV_BLANK);
lsvBlankData.append({voltage, current});
} else if (lsvState == LSV_RUNNING_SAMPLE) {
lsvGraph->addLSVData(voltage, current, GraphWidget::LSV_SAMPLE);
lsvSampleData.append({voltage, current});
}
}
return;
}
if (parts[0] == "RAMP" && parts.size() >= 3) {
bool okIdx, okCurr;
double index = parts[1].toDouble(&okIdx);
double current = parts[2].toDouble(&okCurr);
if (parts[0] == "DATA" && parts.size() >= 6) {
bool okF, okM, okP;
double freq = parts[1].toDouble(&okF);
double mag = parts[4].toDouble(&okM);
double phase = parts[5].toDouble(&okP);
if (okIdx && okCurr) {
// Calculate Voltage based on UI parameters (Host-side calculation)
double start = spinLsvStart->value();
double stop = spinLsvStop->value();
int steps = spinLsvSteps->value();
if (okF && okM && okP) {
// 1. Plot Bode (Magnitude/Phase)
// Note: Phase from AD5940 is in Radians.
rawGraph->addBodeData(freq, mag, phase);
double voltage = start + (index * (stop - start) / steps);
// 2. Convert to Cartesian for Nyquist (Real/Imaginary)
// Z = Mag * e^(j*Phase) = Mag * (cos(Phase) + j*sin(Phase))
// Note: Nyquist Plot expects Z' vs -Z''.
std::complex<double> z = std::polar(mag, phase);
double real = z.real();
double imag =
z.imag(); // This is Z''. GraphWidget negates it for the plot.
if (lsvState == LSV_RUNNING_BLANK) {
lsvGraph->addLSVData(voltage, current, GraphWidget::LSV_BLANK);
lsvBlankData.append({voltage, current});
} else if (lsvState == LSV_RUNNING_SAMPLE) {
lsvGraph->addLSVData(voltage, current, GraphWidget::LSV_SAMPLE);
lsvSampleData.append({voltage, current});
}
}
return;
}
if (parts[0] == "DATA" && parts.size() >= 6) {
bool okF, okR, okI;
double freq = parts[1].toDouble(&okF);
double real = parts[4].toDouble(&okR);
double imag = parts[5].toDouble(&okI);
if (okF && okR && okI) {
double real_plot = real;
double imag_plot = imag;
bool showCorr = false;
if (checkShunt->isChecked()) {
showCorr = true;
double r_shunt = spinShuntRes->value();
std::complex<double> z_meas(real, imag);
std::complex<double> z_shunt(r_shunt, 0.0);
std::complex<double> denom = z_shunt - z_meas;
if (std::abs(denom) > 1e-9) {
std::complex<double> z_cell = (z_meas * z_shunt) / denom;
real_plot = z_cell.real();
imag_plot = z_cell.imag();
} else {
real_plot = 1e9;
imag_plot = 0;
}
}
rawGraph->addBodeData(freq, real_plot, imag_plot);
nyquistGraph->addNyquistData(real, imag, real_plot, imag_plot, showCorr);
sweepFreqs.append(freq);
sweepReals.append(real_plot);
sweepImags.append(imag_plot);
if (sweepReals.size() > 10 && sweepReals.size() % 10 == 0) {
computeHilbert();
performCircleFit();
}
}
nyquistGraph->addNyquistData(real, imag, real, imag, false);
sweepFreqs.append(freq);
sweepReals.append(real);
sweepImags.append(imag);
if (sweepReals.size() > 10 && sweepReals.size() % 10 == 0) {
computeHilbert();
performCircleFit();
}
}
}
}

View File

@ -1,348 +1,443 @@
// File: host/src/MainWindow_UI.cpp
#include "MainWindow.h"
#include <QVBoxLayout>
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QFrame>
#include <QScroller>
#include <QGroupBox>
#include <QVBoxLayout>
void MainWindow::setupUi() {
QWidget *central = new QWidget(this);
setCentralWidget(central);
QVBoxLayout *mainLayout = new QVBoxLayout(central);
mainLayout->setContentsMargins(2, 2, 2, 2);
mainLayout->setSpacing(2);
QWidget *central = new QWidget(this);
setCentralWidget(central);
QVBoxLayout *mainLayout = new QVBoxLayout(central);
mainLayout->setContentsMargins(2, 2, 2, 2);
mainLayout->setSpacing(2);
// ========================================================================
// 1. Global Toolbar (Connection & Hardware Settings)
// ========================================================================
QGroupBox *globalGroup = new QGroupBox("Connection & Hardware", this);
globalGroup->setStyleSheet("QGroupBox { font-weight: bold; border: 1px solid #555; border-radius: 4px; margin-top: 6px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; }");
QVBoxLayout *globalLayout = new QVBoxLayout(globalGroup);
globalLayout->setContentsMargins(4, 12, 4, 4);
globalLayout->setSpacing(4);
// ========================================================================
// 1. Global Toolbar (Connection & Hardware Settings)
// ========================================================================
QGroupBox *globalGroup = new QGroupBox("Connection & Hardware", this);
globalGroup->setStyleSheet(
"QGroupBox { font-weight: bold; border: 1px solid #555; border-radius: "
"4px; margin-top: 6px; } QGroupBox::title { subcontrol-origin: margin; "
"left: 10px; padding: 0 3px; }");
QVBoxLayout *globalLayout = new QVBoxLayout(globalGroup);
globalLayout->setContentsMargins(4, 12, 4, 4);
globalLayout->setSpacing(4);
// Row 1: Connection
QHBoxLayout *connLayout = new QHBoxLayout();
portSelector = new QComboBox(this);
portSelector->setMinimumWidth(120);
connectBtn = new QPushButton("Connect", this);
connectBtn->setStyleSheet("background-color: rgba(0, 128, 0, 128); color: white; font-weight: bold;");
QPushButton *refreshBtn = new QPushButton("Refresh", this);
checkIdBtn = new QPushButton("Check ID", this);
// Row 1: Connection
QHBoxLayout *connLayout = new QHBoxLayout();
portSelector = new QComboBox(this);
portSelector->setMinimumWidth(120);
connectBtn = new QPushButton("Connect", this);
connectBtn->setStyleSheet("background-color: rgba(0, 128, 0, 128); color: "
"white; font-weight: bold;");
QPushButton *refreshBtn = new QPushButton("Refresh", this);
checkIdBtn = new QPushButton("Check ID", this);
connLayout->addWidget(portSelector, 1);
connLayout->addWidget(connectBtn);
connLayout->addWidget(refreshBtn);
connLayout->addWidget(checkIdBtn);
globalLayout->addLayout(connLayout);
connLayout->addWidget(portSelector, 1);
connLayout->addWidget(connectBtn);
connLayout->addWidget(refreshBtn);
connLayout->addWidget(checkIdBtn);
globalLayout->addLayout(connLayout);
// Row 2: Hardware Config
QHBoxLayout *hwLayout = new QHBoxLayout();
// Row 2: Hardware Config
QHBoxLayout *hwLayout = new QHBoxLayout();
comboRangeLP = new QComboBox(this);
comboRangeLP->setToolTip("Low Power TIA Range");
comboRangeLP->addItem("200 Ω", 200);
comboRangeLP->addItem("1 kΩ", 1000);
comboRangeLP->addItem("2 kΩ", 2000);
comboRangeLP->addItem("3 kΩ", 3000);
comboRangeLP->addItem("4 kΩ", 4000);
comboRangeLP->addItem("6 kΩ", 6000);
comboRangeLP->addItem("8 kΩ", 8000);
comboRangeLP->addItem("10 kΩ", 10000);
comboRangeLP->addItem("12 kΩ", 12000);
comboRangeLP->addItem("16 kΩ", 16000);
comboRangeLP->addItem("20 kΩ", 20000);
comboRangeLP->addItem("24 kΩ", 24000);
comboRangeLP->addItem("30 kΩ", 30000);
comboRangeLP->addItem("32 kΩ", 32000);
comboRangeLP->addItem("40 kΩ", 40000);
comboRangeLP->addItem("48 kΩ", 48000);
comboRangeLP->addItem("64 kΩ", 64000);
comboRangeLP->addItem("85 kΩ", 85000);
comboRangeLP->addItem("96 kΩ", 96000);
comboRangeLP->addItem("100 kΩ", 100000);
comboRangeLP->addItem("120 kΩ", 120000);
comboRangeLP->addItem("128 kΩ", 128000);
comboRangeLP->addItem("160 kΩ", 160000);
comboRangeLP->addItem("196 kΩ", 196000);
comboRangeLP->addItem("256 kΩ", 256000);
comboRangeLP->addItem("512 kΩ", 512000);
comboRangeLP->setCurrentIndex(1); // Default 1k
comboRangeLP = new QComboBox(this);
comboRangeLP->setToolTip("Low Power TIA Range");
comboRangeLP->addItem("200 Ω", 200);
comboRangeLP->addItem("1 kΩ", 1000);
comboRangeLP->addItem("2 kΩ", 2000);
comboRangeLP->addItem("3 kΩ", 3000);
comboRangeLP->addItem("4 kΩ", 4000);
comboRangeLP->addItem("6 kΩ", 6000);
comboRangeLP->addItem("8 kΩ", 8000);
comboRangeLP->addItem("10 kΩ", 10000);
comboRangeLP->addItem("12 kΩ", 12000);
comboRangeLP->addItem("16 kΩ", 16000);
comboRangeLP->addItem("20 kΩ", 20000);
comboRangeLP->addItem("24 kΩ", 24000);
comboRangeLP->addItem("30 kΩ", 30000);
comboRangeLP->addItem("32 kΩ", 32000);
comboRangeLP->addItem("40 kΩ", 40000);
comboRangeLP->addItem("48 kΩ", 48000);
comboRangeLP->addItem("64 kΩ", 64000);
comboRangeLP->addItem("85 kΩ", 85000);
comboRangeLP->addItem("96 kΩ", 96000);
comboRangeLP->addItem("100 kΩ", 100000);
comboRangeLP->addItem("120 kΩ", 120000);
comboRangeLP->addItem("128 kΩ", 128000);
comboRangeLP->addItem("160 kΩ", 160000);
comboRangeLP->addItem("196 kΩ", 196000);
comboRangeLP->addItem("256 kΩ", 256000);
comboRangeLP->addItem("512 kΩ", 512000);
comboRangeLP->setCurrentIndex(1); // Default 1k
comboRangeHP = new QComboBox(this);
comboRangeHP->setToolTip("High Speed TIA Range");
comboRangeHP->addItem("200 Ω", 200);
comboRangeHP->addItem("1 kΩ", 1000);
comboRangeHP->addItem("5 kΩ", 5000);
comboRangeHP->addItem("10 kΩ", 10000);
comboRangeHP->addItem("20 kΩ", 20000);
comboRangeHP->addItem("40 kΩ", 40000);
comboRangeHP->addItem("80 kΩ", 80000);
comboRangeHP->addItem("160 kΩ", 160000);
comboRangeHP->setCurrentIndex(1); // Default 1k
comboRangeHP = new QComboBox(this);
comboRangeHP->setToolTip("High Speed TIA Range");
comboRangeHP->addItem("200 Ω", 200);
comboRangeHP->addItem("1 kΩ", 1000);
comboRangeHP->addItem("5 kΩ", 5000);
comboRangeHP->addItem("10 kΩ", 10000);
comboRangeHP->addItem("20 kΩ", 20000);
comboRangeHP->addItem("40 kΩ", 40000);
comboRangeHP->addItem("80 kΩ", 80000);
comboRangeHP->addItem("160 kΩ", 160000);
comboRangeHP->setCurrentIndex(1); // Default 1k
comboRLoad = new QComboBox(this);
comboRLoad->setToolTip("LPTIA Load Resistor (Rload)");
comboRLoad->addItem("10 Ω", 10);
comboRLoad->addItem("30 Ω", 30);
comboRLoad->addItem("50 Ω", 50);
comboRLoad->addItem("100 Ω", 100);
comboRLoad->setCurrentIndex(3); // Default 100R
comboRLoad = new QComboBox(this);
comboRLoad->setToolTip("LPTIA Load Resistor (Rload)");
comboRLoad->addItem("10 Ω", 10);
comboRLoad->addItem("30 Ω", 30);
comboRLoad->addItem("50 Ω", 50);
comboRLoad->addItem("100 Ω", 100);
comboRLoad->setCurrentIndex(3); // Default 100R
calibrateBtn = new QPushButton("Calibrate HW", this);
calibrateBtn->setStyleSheet("background-color: rgba(255, 255, 0, 102); color: white; font-weight: bold;");
calibrateBtn = new QPushButton("Calibrate HW", this);
calibrateBtn->setStyleSheet("background-color: rgba(255, 255, 0, 102); "
"color: white; font-weight: bold;");
checkShortRe0Se0 = new QCheckBox("Short RE0-SE0", this);
checkShortRe0Se0->setToolTip("Internally short Reference and Sense electrodes (2-wire mode)");
// Removed: checkShortRe0Se0
hwLayout->addWidget(new QLabel("LP:"));
hwLayout->addWidget(comboRangeLP, 1);
hwLayout->addWidget(new QLabel("HP:"));
hwLayout->addWidget(comboRangeHP, 1);
hwLayout->addWidget(new QLabel("Rload:"));
hwLayout->addWidget(comboRLoad);
hwLayout->addWidget(checkShortRe0Se0);
hwLayout->addWidget(calibrateBtn);
globalLayout->addLayout(hwLayout);
hwLayout->addWidget(new QLabel("LP:"));
hwLayout->addWidget(comboRangeLP, 1);
hwLayout->addWidget(new QLabel("HP:"));
hwLayout->addWidget(comboRangeHP, 1);
hwLayout->addWidget(new QLabel("Rload:"));
hwLayout->addWidget(comboRLoad);
// Removed: hwLayout->addWidget(checkShortRe0Se0);
hwLayout->addWidget(calibrateBtn);
globalLayout->addLayout(hwLayout);
mainLayout->addWidget(globalGroup);
mainLayout->addWidget(globalGroup);
// ========================================================================
// 2. Main Tab Widget (Modes)
// ========================================================================
mainTabWidget = new QTabWidget(this);
// ========================================================================
// 2. Main Tab Widget (Modes)
// ========================================================================
mainTabWidget = new QTabWidget(this);
// --- Tab 1: Impedance (Sweep & Single) ---
QWidget *impTab = new QWidget();
QVBoxLayout *impLayout = new QVBoxLayout(impTab);
impLayout->setContentsMargins(4, 4, 4, 4);
// --- Tab 1: Impedance (Sweep & Single) ---
QWidget *impTab = new QWidget();
QVBoxLayout *impLayout = new QVBoxLayout(impTab);
impLayout->setContentsMargins(4, 4, 4, 4);
// Controls Area
QGroupBox *impCtrlGroup = new QGroupBox("Impedance Controls", impTab);
QVBoxLayout *impCtrlLayout = new QVBoxLayout(impCtrlGroup);
// Controls Area
QGroupBox *impCtrlGroup = new QGroupBox("Impedance Controls", impTab);
QVBoxLayout *impCtrlLayout = new QVBoxLayout(impCtrlGroup);
// Sweep Row
QHBoxLayout *sweepLayout = new QHBoxLayout();
spinSweepStart = new QDoubleSpinBox(); spinSweepStart->setRange(0.1, 200000.0); spinSweepStart->setValue(1000.0); spinSweepStart->setSuffix(" Hz");
spinSweepStop = new QDoubleSpinBox(); spinSweepStop->setRange(0.1, 200000.0); spinSweepStop->setValue(200000.0); spinSweepStop->setSuffix(" Hz");
spinSweepPPD = new QSpinBox(); spinSweepPPD->setRange(1, 1000); spinSweepPPD->setValue(200); spinSweepPPD->setSuffix(" pts/dec");
sweepBtn = new QPushButton("Sweep");
sweepBtn->setStyleSheet("background-color: rgba(173, 216, 230, 76); color: white; font-weight: bold;");
// Sweep Row
QHBoxLayout *sweepLayout = new QHBoxLayout();
spinSweepStart = new QDoubleSpinBox();
spinSweepStart->setRange(0.1, 200000.0);
spinSweepStart->setValue(1000.0);
spinSweepStart->setSuffix(" Hz");
spinSweepStop = new QDoubleSpinBox();
spinSweepStop->setRange(0.1, 200000.0);
spinSweepStop->setValue(200000.0);
spinSweepStop->setSuffix(" Hz");
spinSweepPPD = new QSpinBox();
spinSweepPPD->setRange(1, 1000);
spinSweepPPD->setValue(200);
spinSweepPPD->setSuffix(" pts/dec");
sweepBtn = new QPushButton("Sweep");
sweepBtn->setStyleSheet("background-color: rgba(173, 216, 230, 76); color: "
"white; font-weight: bold;");
sweepLayout->addWidget(new QLabel("Start:")); sweepLayout->addWidget(spinSweepStart);
sweepLayout->addWidget(new QLabel("Stop:")); sweepLayout->addWidget(spinSweepStop);
sweepLayout->addWidget(new QLabel("PPD:")); sweepLayout->addWidget(spinSweepPPD);
sweepLayout->addWidget(sweepBtn);
impCtrlLayout->addLayout(sweepLayout);
sweepLayout->addWidget(new QLabel("Start:"));
sweepLayout->addWidget(spinSweepStart);
sweepLayout->addWidget(new QLabel("Stop:"));
sweepLayout->addWidget(spinSweepStop);
sweepLayout->addWidget(new QLabel("PPD:"));
sweepLayout->addWidget(spinSweepPPD);
sweepLayout->addWidget(sweepBtn);
impCtrlLayout->addLayout(sweepLayout);
// Single Freq & Calibration Row
QHBoxLayout *singleLayout = new QHBoxLayout();
spinFreq = new QDoubleSpinBox(); spinFreq->setRange(0.1, 200000.0); spinFreq->setValue(1000.0); spinFreq->setSuffix(" Hz");
measureBtn = new QPushButton("Measure");
measureBtn->setStyleSheet("background-color: rgba(0, 100, 0, 76); color: white; font-weight: bold;");
// Single Freq & Calibration Row
QHBoxLayout *singleLayout = new QHBoxLayout();
spinFreq = new QDoubleSpinBox();
spinFreq->setRange(0.1, 200000.0);
spinFreq->setValue(1000.0);
spinFreq->setSuffix(" Hz");
checkShunt = new QCheckBox("Shunt");
spinShuntRes = new QDoubleSpinBox(); spinShuntRes->setRange(1.0, 1000000.0); spinShuntRes->setValue(466.0); spinShuntRes->setSuffix(" Ω");
// New Bias Control
spinImpBias = new QDoubleSpinBox();
spinImpBias->setRange(-3000.0, 3000.0);
spinImpBias->setValue(0.0);
spinImpBias->setSuffix(" mV");
spinImpBias->setToolTip("DC Bias Voltage");
spinCondStd = new QDoubleSpinBox(); spinCondStd->setRange(0.0, 1000000.0); spinCondStd->setValue(1413.0); spinCondStd->setSuffix(" µS/cm");
btnCalCond = new QPushButton("Cal K");
btnCalCond->setStyleSheet("background-color: rgba(255, 165, 0, 102); color: white; font-weight: bold;");
measureBtn = new QPushButton("Measure");
measureBtn->setStyleSheet("background-color: rgba(0, 100, 0, 76); color: "
"white; font-weight: bold;");
singleLayout->addWidget(new QLabel("Freq:")); singleLayout->addWidget(spinFreq);
singleLayout->addWidget(measureBtn);
singleLayout->addWidget(checkShunt); singleLayout->addWidget(spinShuntRes);
singleLayout->addWidget(new QLabel("Std:")); singleLayout->addWidget(spinCondStd);
singleLayout->addWidget(btnCalCond);
impCtrlLayout->addLayout(singleLayout);
spinCondStd = new QDoubleSpinBox();
spinCondStd->setRange(0.0, 1000000.0);
spinCondStd->setValue(1413.0);
spinCondStd->setSuffix(" µS/cm");
btnCalCond = new QPushButton("Cal K");
btnCalCond->setStyleSheet("background-color: rgba(255, 165, 0, 102); color: "
"white; font-weight: bold;");
// Results Row
QHBoxLayout *resLayout = new QHBoxLayout();
lblResultRs = new QLabel(" Rs: -- Ω");
lblResultRs->setStyleSheet("font-weight: bold; color: #FFD700; font-size: 14px;");
lblResultCond = new QLabel("Cond: -- µS/cm");
lblResultCond->setStyleSheet("font-weight: bold; color: #00FFFF; font-size: 14px;");
resLayout->addWidget(lblResultRs);
resLayout->addWidget(lblResultCond);
resLayout->addStretch();
impCtrlLayout->addLayout(resLayout);
singleLayout->addWidget(new QLabel("Freq:"));
singleLayout->addWidget(spinFreq);
singleLayout->addWidget(new QLabel("Bias:"));
singleLayout->addWidget(spinImpBias);
singleLayout->addWidget(measureBtn);
// Removed Shunt controls
singleLayout->addWidget(new QLabel("Std:"));
singleLayout->addWidget(spinCondStd);
singleLayout->addWidget(btnCalCond);
impCtrlLayout->addLayout(singleLayout);
impLayout->addWidget(impCtrlGroup);
// Results Row
QHBoxLayout *resLayout = new QHBoxLayout();
lblResultRs = new QLabel(" Rs: -- Ω");
lblResultRs->setStyleSheet(
"font-weight: bold; color: #FFD700; font-size: 14px;");
lblResultCond = new QLabel("Cond: -- µS/cm");
lblResultCond->setStyleSheet(
"font-weight: bold; color: #00FFFF; font-size: 14px;");
resLayout->addWidget(lblResultRs);
resLayout->addWidget(lblResultCond);
resLayout->addStretch();
impCtrlLayout->addLayout(resLayout);
// Graphs (Nested Tab)
impGraphTabs = new QTabWidget();
rawGraph = new GraphWidget(this);
rawGraph->configureRawPlot();
nyquistGraph = new GraphWidget(this);
nyquistGraph->configureNyquistPlot();
impLayout->addWidget(impCtrlGroup);
impGraphTabs->addTab(rawGraph, "Bode Plot");
impGraphTabs->addTab(nyquistGraph, "Nyquist Plot");
impLayout->addWidget(impGraphTabs);
// Graphs (Nested Tab)
impGraphTabs = new QTabWidget();
rawGraph = new GraphWidget(this);
rawGraph->configureRawPlot();
nyquistGraph = new GraphWidget(this);
nyquistGraph->configureNyquistPlot();
mainTabWidget->addTab(impTab, "Impedance");
impGraphTabs->addTab(rawGraph, "Bode Plot");
impGraphTabs->addTab(nyquistGraph, "Nyquist Plot");
impLayout->addWidget(impGraphTabs);
// --- Tab 2: Amperometry ---
QWidget *ampTab = new QWidget();
QVBoxLayout *ampLayout = new QVBoxLayout(ampTab);
ampLayout->setContentsMargins(4, 4, 4, 4);
mainTabWidget->addTab(impTab, "Impedance");
QGroupBox *ampCtrlGroup = new QGroupBox("Amperometry Controls", ampTab);
QHBoxLayout *ampCtrlLayout = new QHBoxLayout(ampCtrlGroup);
// --- Tab 2: Amperometry ---
QWidget *ampTab = new QWidget();
QVBoxLayout *ampLayout = new QVBoxLayout(ampTab);
ampLayout->setContentsMargins(4, 4, 4, 4);
spinAmpBias = new QDoubleSpinBox(); spinAmpBias->setRange(-1100.0, 1100.0); spinAmpBias->setValue(0.0); spinAmpBias->setSuffix(" mV");
comboLPF = new QComboBox();
comboLPF->addItem("Bypass", 0);
comboLPF->addItem("20kΩ (8Hz)", 1);
comboLPF->addItem("100kΩ (1.6Hz)", 2);
comboLPF->addItem("200kΩ (0.8Hz)", 3);
comboLPF->addItem("400kΩ (0.4Hz)", 4);
comboLPF->addItem("600kΩ (0.26Hz)", 5);
comboLPF->addItem("1MΩ (0.16Hz)", 6);
comboLPF->setCurrentIndex(1);
QGroupBox *ampCtrlGroup = new QGroupBox("Amperometry Controls", ampTab);
QHBoxLayout *ampCtrlLayout = new QHBoxLayout(ampCtrlGroup);
ampBtn = new QPushButton("Start Amp");
ampBtn->setStyleSheet("background-color: rgba(238, 130, 238, 51); color: white; font-weight: bold;");
spinAmpBias = new QDoubleSpinBox();
spinAmpBias->setRange(-3000.0, 3000.0);
spinAmpBias->setValue(0.0);
spinAmpBias->setSuffix(" mV");
comboLPF = new QComboBox();
comboLPF->addItem("Bypass", 0);
comboLPF->addItem("20kΩ (8Hz)", 1);
comboLPF->addItem("100kΩ (1.6Hz)", 2);
comboLPF->addItem("200kΩ (0.8Hz)", 3);
comboLPF->addItem("400kΩ (0.4Hz)", 4);
comboLPF->addItem("600kΩ (0.26Hz)", 5);
comboLPF->addItem("1MΩ (0.16Hz)", 6);
comboLPF->setCurrentIndex(1);
ampCtrlLayout->addWidget(new QLabel("Bias:")); ampCtrlLayout->addWidget(spinAmpBias);
ampCtrlLayout->addWidget(new QLabel("LPF:")); ampCtrlLayout->addWidget(comboLPF);
ampCtrlLayout->addWidget(ampBtn);
ampBtn = new QPushButton("Start Amp");
ampBtn->setStyleSheet("background-color: rgba(238, 130, 238, 51); color: "
"white; font-weight: bold;");
ampLayout->addWidget(ampCtrlGroup);
ampCtrlLayout->addWidget(new QLabel("Bias:"));
ampCtrlLayout->addWidget(spinAmpBias);
ampCtrlLayout->addWidget(new QLabel("LPF:"));
ampCtrlLayout->addWidget(comboLPF);
ampCtrlLayout->addWidget(ampBtn);
ampGraph = new GraphWidget(this);
ampGraph->configureAmperometricPlot();
ampLayout->addWidget(ampGraph);
ampLayout->addWidget(ampCtrlGroup);
mainTabWidget->addTab(ampTab, "Amperometry");
ampGraph = new GraphWidget(this);
ampGraph->configureAmperometricPlot();
ampLayout->addWidget(ampGraph);
// --- Tab 3: Voltammetry (LSV) ---
QWidget *lsvTab = new QWidget();
QVBoxLayout *lsvLayout = new QVBoxLayout(lsvTab);
lsvLayout->setContentsMargins(4, 4, 4, 4);
mainTabWidget->addTab(ampTab, "Amperometry");
QGroupBox *lsvCtrlGroup = new QGroupBox("LSV Controls", lsvTab);
QHBoxLayout *lsvCtrlLayout = new QHBoxLayout(lsvCtrlGroup);
// --- Tab 3: Voltammetry (LSV) ---
QWidget *lsvTab = new QWidget();
QVBoxLayout *lsvLayout = new QVBoxLayout(lsvTab);
lsvLayout->setContentsMargins(4, 4, 4, 4);
spinLsvStart = new QDoubleSpinBox(); spinLsvStart->setRange(-1100.0, 1100.0); spinLsvStart->setValue(800.0); spinLsvStart->setSuffix(" mV");
spinLsvStop = new QDoubleSpinBox(); spinLsvStop->setRange(-1100.0, 1100.0); spinLsvStop->setValue(-200.0); spinLsvStop->setSuffix(" mV");
spinLsvSteps = new QSpinBox(); spinLsvSteps->setRange(10, 4000); spinLsvSteps->setValue(200); spinLsvSteps->setSuffix(" pts");
spinLsvDuration = new QSpinBox(); spinLsvDuration->setRange(100, 600000); spinLsvDuration->setValue(10000); spinLsvDuration->setSuffix(" ms");
QGroupBox *lsvCtrlGroup = new QGroupBox("LSV Controls", lsvTab);
QHBoxLayout *lsvCtrlLayout = new QHBoxLayout(lsvCtrlGroup);
lsvBlankBtn = new QPushButton("Run Blank");
lsvBlankBtn->setStyleSheet("background-color: rgba(0, 0, 0, 255); color: white; font-weight: bold;");
lsvSampleBtn = new QPushButton("Run Sample");
lsvSampleBtn->setStyleSheet("background-color: rgba(75, 0, 130, 76); color: white; font-weight: bold;");
spinLsvStart = new QDoubleSpinBox();
spinLsvStart->setRange(-3000.0, 3000.0);
spinLsvStart->setValue(800.0);
spinLsvStart->setSuffix(" mV");
spinLsvStop = new QDoubleSpinBox();
spinLsvStop->setRange(-3000.0, 3000.0);
spinLsvStop->setValue(-200.0);
spinLsvStop->setSuffix(" mV");
spinLsvSteps = new QSpinBox();
spinLsvSteps->setRange(10, 4000);
spinLsvSteps->setValue(200);
spinLsvSteps->setSuffix(" pts");
spinLsvDuration = new QSpinBox();
spinLsvDuration->setRange(100, 600000);
spinLsvDuration->setValue(10000);
spinLsvDuration->setSuffix(" ms");
lsvCtrlLayout->addWidget(new QLabel("Start:")); lsvCtrlLayout->addWidget(spinLsvStart);
lsvCtrlLayout->addWidget(new QLabel("Stop:")); lsvCtrlLayout->addWidget(spinLsvStop);
lsvCtrlLayout->addWidget(new QLabel("Steps:")); lsvCtrlLayout->addWidget(spinLsvSteps);
lsvCtrlLayout->addWidget(new QLabel("Time:")); lsvCtrlLayout->addWidget(spinLsvDuration);
lsvCtrlLayout->addWidget(lsvBlankBtn);
lsvCtrlLayout->addWidget(lsvSampleBtn);
lsvBlankBtn = new QPushButton("Run Blank");
lsvBlankBtn->setStyleSheet(
"background-color: rgba(0, 0, 0, 255); color: white; font-weight: bold;");
lsvSampleBtn = new QPushButton("Run Sample");
lsvSampleBtn->setStyleSheet("background-color: rgba(75, 0, 130, 76); color: "
"white; font-weight: bold;");
lsvLayout->addWidget(lsvCtrlGroup);
lsvCtrlLayout->addWidget(new QLabel("Start:"));
lsvCtrlLayout->addWidget(spinLsvStart);
lsvCtrlLayout->addWidget(new QLabel("Stop:"));
lsvCtrlLayout->addWidget(spinLsvStop);
lsvCtrlLayout->addWidget(new QLabel("Steps:"));
lsvCtrlLayout->addWidget(spinLsvSteps);
lsvCtrlLayout->addWidget(new QLabel("Time:"));
lsvCtrlLayout->addWidget(spinLsvDuration);
lsvCtrlLayout->addWidget(lsvBlankBtn);
lsvCtrlLayout->addWidget(lsvSampleBtn);
lsvGraph = new GraphWidget(this);
lsvGraph->configureLSVPlot();
lsvLayout->addWidget(lsvGraph);
lsvLayout->addWidget(lsvCtrlGroup);
mainTabWidget->addTab(lsvTab, "Voltammetry");
lsvGraph = new GraphWidget(this);
lsvGraph->configureLSVPlot();
lsvLayout->addWidget(lsvGraph);
// ========================================================================
// 3. Log Widget (Bottom)
// ========================================================================
logWidget = new QTextEdit(this);
logWidget->setReadOnly(true);
logWidget->setFont(QFont("Monospace"));
logWidget->setPlaceholderText("Scanning for 0xCAFE EIS Device...");
logWidget->setStyleSheet("background-color: #1E1E1E; color: #00FF00; border: 1px solid #444;");
logWidget->setMaximumHeight(150);
QScroller::grabGesture(logWidget->viewport(), QScroller::TouchGesture);
mainTabWidget->addTab(lsvTab, "Voltammetry");
// Add to Main Layout
QSplitter *splitter = new QSplitter(Qt::Vertical, this);
splitter->addWidget(mainTabWidget);
splitter->addWidget(logWidget);
splitter->setStretchFactor(0, 4);
splitter->setStretchFactor(1, 1);
// ========================================================================
// 3. Log Widget (Bottom)
// ========================================================================
logWidget = new QTextEdit(this);
logWidget->setReadOnly(true);
logWidget->setFont(QFont("Monospace"));
logWidget->setPlaceholderText("Scanning for 0xCAFE EIS Device...");
logWidget->setStyleSheet(
"background-color: #1E1E1E; color: #00FF00; border: 1px solid #444;");
logWidget->setMaximumHeight(150);
QScroller::grabGesture(logWidget->viewport(), QScroller::TouchGesture);
mainLayout->addWidget(splitter);
// Add to Main Layout
QSplitter *splitter = new QSplitter(Qt::Vertical, this);
splitter->addWidget(mainTabWidget);
splitter->addWidget(logWidget);
splitter->setStretchFactor(0, 4);
splitter->setStretchFactor(1, 1);
// ========================================================================
// Initial State & Connections
// ========================================================================
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
checkShortRe0Se0->setEnabled(false);
comboRangeLP->setEnabled(false);
comboRangeHP->setEnabled(false);
comboRLoad->setEnabled(false);
mainLayout->addWidget(splitter);
// Disable all mode controls initially
impTab->setEnabled(false);
ampTab->setEnabled(false);
lsvTab->setEnabled(false);
// ========================================================================
// Initial State & Connections
// ========================================================================
checkIdBtn->setEnabled(false);
calibrateBtn->setEnabled(false);
// Removed: checkShortRe0Se0->setEnabled(false);
comboRangeLP->setEnabled(false);
comboRangeHP->setEnabled(false);
comboRLoad->setEnabled(false);
// 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(checkShortRe0Se0, &QCheckBox::toggled, this, &MainWindow::onShortRe0Se0Toggled);
// Disable all mode controls initially
impTab->setEnabled(false);
ampTab->setEnabled(false);
lsvTab->setEnabled(false);
connect(sweepBtn, &QPushButton::clicked, this, &MainWindow::startSweep);
connect(measureBtn, &QPushButton::clicked, this, &MainWindow::toggleMeasurement);
connect(ampBtn, &QPushButton::clicked, this, &MainWindow::toggleAmperometry);
connect(lsvBlankBtn, &QPushButton::clicked, this, &MainWindow::startLSVBlank);
connect(lsvSampleBtn, &QPushButton::clicked, this, &MainWindow::startLSVSample);
connect(btnCalCond, &QPushButton::clicked, this, &MainWindow::calibrateCellConstant);
connect(comboLPF, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::onLPFChanged);
connect(comboRLoad, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::onRLoadChanged);
// 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);
// Removed: connect(checkShortRe0Se0, ...)
connect(mainTabWidget, &QTabWidget::currentChanged, this, [this](int index){
if (index == 0) rawGraph->configureRawPlot();
else if (index == 1) ampGraph->configureAmperometricPlot();
else if (index == 2) lsvGraph->configureLSVPlot();
});
connect(sweepBtn, &QPushButton::clicked, this, &MainWindow::startSweep);
connect(measureBtn, &QPushButton::clicked, this,
&MainWindow::toggleMeasurement);
connect(ampBtn, &QPushButton::clicked, this, &MainWindow::toggleAmperometry);
connect(lsvBlankBtn, &QPushButton::clicked, this, &MainWindow::startLSVBlank);
connect(lsvSampleBtn, &QPushButton::clicked, this,
&MainWindow::startLSVSample);
connect(btnCalCond, &QPushButton::clicked, this,
&MainWindow::calibrateCellConstant);
connect(comboLPF, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&MainWindow::onLPFChanged);
connect(comboRLoad, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&MainWindow::onRLoadChanged);
connect(mainTabWidget, &QTabWidget::currentChanged, this, [this](int index) {
if (index == 0)
rawGraph->configureRawPlot();
else if (index == 1)
ampGraph->configureAmperometricPlot();
else if (index == 2)
lsvGraph->configureLSVPlot();
});
}
void MainWindow::setButtonBlinking(QPushButton *btn, bool blinking) {
if (activeButton && activeButton != btn) {
// Restore original colors manually based on button type
if (activeButton == sweepBtn) activeButton->setStyleSheet("background-color: rgba(173, 216, 230, 76); color: white; font-weight: bold;");
else if (activeButton == measureBtn) activeButton->setStyleSheet("background-color: rgba(0, 100, 0, 76); color: white; font-weight: bold;");
else if (activeButton == ampBtn) activeButton->setStyleSheet("background-color: rgba(238, 130, 238, 51); color: white; font-weight: bold;");
else if (activeButton == lsvBlankBtn) activeButton->setStyleSheet("background-color: rgba(0, 0, 0, 255); color: white; font-weight: bold;");
else if (activeButton == lsvSampleBtn) activeButton->setStyleSheet("background-color: rgba(75, 0, 130, 76); color: white; font-weight: bold;");
}
if (activeButton && activeButton != btn) {
// Restore original colors manually based on button type
if (activeButton == sweepBtn)
activeButton->setStyleSheet("background-color: rgba(173, 216, 230, 76); "
"color: white; font-weight: bold;");
else if (activeButton == measureBtn)
activeButton->setStyleSheet("background-color: rgba(0, 100, 0, 76); "
"color: white; font-weight: bold;");
else if (activeButton == ampBtn)
activeButton->setStyleSheet("background-color: rgba(238, 130, 238, 51); "
"color: white; font-weight: bold;");
else if (activeButton == lsvBlankBtn)
activeButton->setStyleSheet("background-color: rgba(0, 0, 0, 255); "
"color: white; font-weight: bold;");
else if (activeButton == lsvSampleBtn)
activeButton->setStyleSheet("background-color: rgba(75, 0, 130, 76); "
"color: white; font-weight: bold;");
}
activeButton = btn;
activeButton = btn;
if (blinking) {
blinkTimer->start();
} else {
blinkTimer->stop();
if (activeButton) {
// Restore original colors manually
if (activeButton == sweepBtn) activeButton->setStyleSheet("background-color: rgba(173, 216, 230, 76); color: white; font-weight: bold;");
else if (activeButton == measureBtn) activeButton->setStyleSheet("background-color: rgba(0, 100, 0, 76); color: white; font-weight: bold;");
else if (activeButton == ampBtn) activeButton->setStyleSheet("background-color: rgba(238, 130, 238, 51); color: white; font-weight: bold;");
else if (activeButton == lsvBlankBtn) activeButton->setStyleSheet("background-color: rgba(0, 0, 0, 255); color: white; font-weight: bold;");
else if (activeButton == lsvSampleBtn) activeButton->setStyleSheet("background-color: rgba(75, 0, 130, 76); color: white; font-weight: bold;");
}
activeButton = nullptr;
if (blinking) {
blinkTimer->start();
} else {
blinkTimer->stop();
if (activeButton) {
// Restore original colors manually
if (activeButton == sweepBtn)
activeButton->setStyleSheet("background-color: rgba(173, 216, 230, "
"76); color: white; font-weight: bold;");
else if (activeButton == measureBtn)
activeButton->setStyleSheet("background-color: rgba(0, 100, 0, 76); "
"color: white; font-weight: bold;");
else if (activeButton == ampBtn)
activeButton->setStyleSheet("background-color: rgba(238, 130, 238, "
"51); color: white; font-weight: bold;");
else if (activeButton == lsvBlankBtn)
activeButton->setStyleSheet("background-color: rgba(0, 0, 0, 255); "
"color: white; font-weight: bold;");
else if (activeButton == lsvSampleBtn)
activeButton->setStyleSheet("background-color: rgba(75, 0, 130, 76); "
"color: white; font-weight: bold;");
}
activeButton = nullptr;
}
}
void MainWindow::onBlinkTimer() {
if (!activeButton) return;
if (!activeButton)
return;
blinkState = !blinkState;
if (blinkState) {
activeButton->setStyleSheet("background-color: #FF0000; color: white; border: 1px solid #FF4444; font-weight: bold;");
} else {
activeButton->setStyleSheet("background-color: #880000; color: white; border: 1px solid #AA0000; font-weight: bold;");
}
blinkState = !blinkState;
if (blinkState) {
activeButton->setStyleSheet(
"background-color: #FF0000; color: white; border: 1px solid #FF4444; "
"font-weight: bold;");
} else {
activeButton->setStyleSheet(
"background-color: #880000; color: white; border: 1px solid #AA0000; "
"font-weight: bold;");
}
}

337
main.c
View File

@ -20,170 +20,207 @@ char input_buffer[64];
int input_pos = 0;
void process_command() {
char cmd = input_buffer[0];
sleep_ms(10);
char cmd = input_buffer[0];
sleep_ms(10);
if (cmd == 'v') {
uint32_t id = AD5940_ReadReg(REG_AFECON_CHIPID);
printf("CHIP_ID:0x%04X\n", id);
if (cmd == 'v') {
uint32_t id = AD5940_ReadReg(REG_AFECON_CHIPID);
printf("CHIP_ID:0x%04X\n", id);
} else if (cmd == 'r') {
if (strlen(input_buffer) > 2) {
int lp = 0, hp = 0;
int count = sscanf(input_buffer + 2, "%d %d", &lp, &hp);
if (count >= 1)
ConfigLptiaVal = lp;
if (count >= 2)
ConfigHstiaVal = hp;
printf("RANGE_SET LP:%d HP:%d\n", ConfigLptiaVal, ConfigHstiaVal);
}
else if (cmd == 'r') {
if (strlen(input_buffer) > 2) {
int lp = 0, hp = 0;
int count = sscanf(input_buffer + 2, "%d %d", &lp, &hp);
if (count >= 1) ConfigLptiaVal = lp;
if (count >= 2) ConfigHstiaVal = hp;
printf("RANGE_SET LP:%d HP:%d\n", ConfigLptiaVal, ConfigHstiaVal);
}
} else if (cmd == 'f') {
if (strlen(input_buffer) > 2) {
int idx = atoi(input_buffer + 2);
switch (idx) {
case 0:
CurrentLpTiaRf = LPTIARF_BYPASS;
break;
case 1:
CurrentLpTiaRf = LPTIARF_20K;
break;
case 2:
CurrentLpTiaRf = LPTIARF_100K;
break;
case 3:
CurrentLpTiaRf = LPTIARF_200K;
break;
case 4:
CurrentLpTiaRf = LPTIARF_400K;
break;
case 5:
CurrentLpTiaRf = LPTIARF_600K;
break;
case 6:
CurrentLpTiaRf = LPTIARF_1M;
break;
default:
CurrentLpTiaRf = LPTIARF_20K;
break;
}
printf("LPF_SET:%d\n", idx);
}
else if (cmd == 'f') {
if (strlen(input_buffer) > 2) {
int idx = atoi(input_buffer + 2);
switch(idx) {
case 0: CurrentLpTiaRf = LPTIARF_BYPASS; break;
case 1: CurrentLpTiaRf = LPTIARF_20K; break;
case 2: CurrentLpTiaRf = LPTIARF_100K; break;
case 3: CurrentLpTiaRf = LPTIARF_200K; break;
case 4: CurrentLpTiaRf = LPTIARF_400K; break;
case 5: CurrentLpTiaRf = LPTIARF_600K; break;
case 6: CurrentLpTiaRf = LPTIARF_1M; break;
default: CurrentLpTiaRf = LPTIARF_20K; break;
}
printf("LPF_SET:%d\n", idx);
}
} else if (cmd == 'L') { // Set RLoad
if (strlen(input_buffer) > 2) {
int val = atoi(input_buffer + 2);
switch (val) {
case 10:
ConfigRLoad = LPTIARLOAD_10R;
break;
case 30:
ConfigRLoad = LPTIARLOAD_30R;
break;
case 50:
ConfigRLoad = LPTIARLOAD_50R;
break;
case 100:
ConfigRLoad = LPTIARLOAD_100R;
break;
default:
ConfigRLoad = LPTIARLOAD_100R;
break;
}
printf("RLOAD_SET:%d\n", val);
}
else if (cmd == 'L') { // Set RLoad
if (strlen(input_buffer) > 2) {
int val = atoi(input_buffer + 2);
switch(val) {
case 10: ConfigRLoad = LPTIARLOAD_10R; break;
case 30: ConfigRLoad = LPTIARLOAD_30R; break;
case 50: ConfigRLoad = LPTIARLOAD_50R; break;
case 100: ConfigRLoad = LPTIARLOAD_100R; break;
default: ConfigRLoad = LPTIARLOAD_100R; break;
}
printf("RLOAD_SET:%d\n", val);
}
} else if (cmd == 't') {
if (strlen(input_buffer) > 2) {
int val = atoi(input_buffer + 2);
GlobalShortRe0Se0 = (val > 0) ? bTRUE : bFALSE;
printf("SHORT_RE0_SE0:%d\n", GlobalShortRe0Se0);
}
else if (cmd == 't') {
if (strlen(input_buffer) > 2) {
int val = atoi(input_buffer + 2);
GlobalShortRe0Se0 = (val > 0) ? bTRUE : bFALSE;
printf("SHORT_RE0_SE0:%d\n", GlobalShortRe0Se0);
}
} else if (cmd == 'c') {
Routine_CalibrateLFO();
Routine_CalibrateSystem();
} else if (cmd == 'm') {
float freq = 1000.0f;
float bias = 0.0f;
if (strlen(input_buffer) > 2) {
int count = sscanf(input_buffer + 2, "%f %f", &freq, &bias);
// If only one arg provided, freq is set, bias remains 0.
}
else if (cmd == 'c') {
Routine_CalibrateLFO();
Routine_CalibrateSystem();
}
else if (cmd == 'm') {
float freq = 1000.0f;
if (strlen(input_buffer) > 2) freq = atof(input_buffer + 2);
Routine_Measure(freq);
}
else if (cmd == 's') {
float start = 100.0f, end = 100000.0f;
int steps = 50;
if (strlen(input_buffer) > 2) sscanf(input_buffer + 2, "%f %f %d", &start, &end, &steps);
Routine_Sweep(start, end, steps);
}
else if (cmd == 'a') {
float bias = 0.0f;
if (strlen(input_buffer) > 2) bias = atof(input_buffer + 2);
Routine_Amperometric(bias);
}
else if (cmd == 'l') {
float start = -500.0f, end = 500.0f;
int steps = 100, duration = 10000;
if (strlen(input_buffer) > 2) sscanf(input_buffer + 2, "%f %f %d %d", &start, &end, &steps, &duration);
Routine_LSV(start, end, steps, duration);
}
else if (cmd == 'x') {
if (CurrentMode == MODE_IMPEDANCE) AppIMPCleanup();
else if (CurrentMode == MODE_AMPEROMETRIC) AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
else if (CurrentMode == MODE_RAMP) AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
printf("STOPPED\n");
SystemReset();
}
else if (cmd == 'z') {
SystemReset();
// Routine_Measure needs to handle bias now.
// We'll update Routine_Measure momentarily.
// For now, let's just pass freq as before, but update Routine_Measure
// signature.
Routine_Measure(freq, bias);
} else if (cmd == 's') {
float start = 100.0f, end = 100000.0f;
int steps = 50;
float bias = 0.0f;
if (strlen(input_buffer) > 2) {
int count =
sscanf(input_buffer + 2, "%f %f %d %f", &start, &end, &steps, &bias);
// defaults handled by initialization
}
Routine_Sweep(start, end, steps, bias);
} else if (cmd == 'a') {
float bias = 0.0f;
if (strlen(input_buffer) > 2)
bias = atof(input_buffer + 2);
Routine_Amperometric(bias);
} else if (cmd == 'l') {
float start = -500.0f, end = 500.0f;
int steps = 100, duration = 10000;
if (strlen(input_buffer) > 2)
sscanf(input_buffer + 2, "%f %f %d %d", &start, &end, &steps, &duration);
Routine_LSV(start, end, steps, duration);
} else if (cmd == 'x') {
if (CurrentMode == MODE_IMPEDANCE)
AppIMPCleanup();
else if (CurrentMode == MODE_AMPEROMETRIC)
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
else if (CurrentMode == MODE_RAMP)
AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
printf("STOPPED\n");
SystemReset();
} else if (cmd == 'z') {
SystemReset();
}
}
int main() {
stdio_init_all();
sleep_ms(2000);
stdio_init_all();
sleep_ms(2000);
setup_pins();
setup_pins();
AD5940_HWReset();
AD5940_Initialize();
AD5940PlatformCfg();
AD5940_HWReset();
AD5940_Initialize();
AD5940PlatformCfg();
Routine_CalibrateLFO();
Routine_CalibrateLFO();
printf("SYSTEM_READY\n");
printf("SYSTEM_READY\n");
while (true) {
int c = getchar_timeout_us(0);
if (c != PICO_ERROR_TIMEOUT) {
if (c == '\n' || c == '\r') {
input_buffer[input_pos] = 0;
if (input_pos > 0) process_command();
input_pos = 0;
} else if (input_pos < 63) {
input_buffer[input_pos++] = (char)c;
}
}
if (gpio_get(PIN_INT) == 0) {
uint32_t temp = APPBUFF_SIZE;
int32_t status = 0;
if (CurrentMode == MODE_IMPEDANCE) {
status = AppIMPISR(AppBuff, &temp);
if (status == AD5940ERR_FIFO) {
printf("ERROR: FIFO Overflow/Underflow. Stopping.\n");
AppIMPCleanup();
CurrentMode = MODE_IDLE;
SystemReset();
} else if(temp > 0) {
ImpedanceShowResult(AppBuff, temp);
}
}
else if (CurrentMode == MODE_AMPEROMETRIC) {
status = AppAMPISR(AppBuff, &temp);
if (status == AD5940ERR_FIFO) {
printf("ERROR: FIFO Overflow/Underflow. Stopping.\n");
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
SystemReset();
} else if(temp > 0) {
AmperometricShowResult((float*)AppBuff, temp);
}
}
else if (CurrentMode == MODE_RAMP) {
status = AppRAMPISR(AppBuff, &temp);
if (status == AD5940ERR_FIFO) {
printf("ERROR: FIFO Overflow/Underflow. Stopping.\n");
AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
SystemReset();
} else if(temp > 0) {
RampShowResult((float*)AppBuff, temp);
}
}
if (status == AD5940ERR_STOP) {
printf("STOPPED\n");
if (CurrentMode == MODE_IMPEDANCE) AppIMPCleanup();
else if (CurrentMode == MODE_AMPEROMETRIC) AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
else if (CurrentMode == MODE_RAMP) AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
SystemReset();
}
}
while (true) {
int c = getchar_timeout_us(0);
if (c != PICO_ERROR_TIMEOUT) {
if (c == '\n' || c == '\r') {
input_buffer[input_pos] = 0;
if (input_pos > 0)
process_command();
input_pos = 0;
} else if (input_pos < 63) {
input_buffer[input_pos++] = (char)c;
}
}
return 0;
if (gpio_get(PIN_INT) == 0) {
uint32_t temp = APPBUFF_SIZE;
int32_t status = 0;
if (CurrentMode == MODE_IMPEDANCE) {
status = AppIMPISR(AppBuff, &temp);
if (status == AD5940ERR_FIFO) {
printf("ERROR: FIFO Overflow/Underflow. Stopping.\n");
AppIMPCleanup();
CurrentMode = MODE_IDLE;
SystemReset();
} else if (temp > 0) {
ImpedanceShowResult(AppBuff, temp);
}
} else if (CurrentMode == MODE_AMPEROMETRIC) {
status = AppAMPISR(AppBuff, &temp);
if (status == AD5940ERR_FIFO) {
printf("ERROR: FIFO Overflow/Underflow. Stopping.\n");
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
SystemReset();
} else if (temp > 0) {
AmperometricShowResult((float *)AppBuff, temp);
}
} else if (CurrentMode == MODE_RAMP) {
status = AppRAMPISR(AppBuff, &temp);
if (status == AD5940ERR_FIFO) {
printf("ERROR: FIFO Overflow/Underflow. Stopping.\n");
AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
SystemReset();
} else if (temp > 0) {
RampShowResult((float *)AppBuff, temp);
}
}
if (status == AD5940ERR_STOP) {
printf("STOPPED\n");
if (CurrentMode == MODE_IMPEDANCE)
AppIMPCleanup();
else if (CurrentMode == MODE_AMPEROMETRIC)
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
else if (CurrentMode == MODE_RAMP)
AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
SystemReset();
}
}
}
return 0;
}

59
rp2040port.c Normal file
View File

@ -0,0 +1,59 @@
#include "rp2040port.h"
// --- Hardware Setup ---
void setup_pins(void) {
// SPI Initialisation. Using SPI0 at 16MHz.
spi_init(spi0, 16000000);
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
// Chip Select
gpio_init(PIN_CS);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_put(PIN_CS, 1);
// Reset Pin
gpio_init(PIN_RST);
gpio_set_dir(PIN_RST, GPIO_OUT);
gpio_put(PIN_RST, 1);
// Interrupt Pin
gpio_init(PIN_INT);
gpio_set_dir(PIN_INT, GPIO_IN);
gpio_pull_up(PIN_INT);
}
// --- Platform Interface Implementation ---
void AD5940_CsClr(void) { gpio_put(PIN_CS, 0); }
void AD5940_CsSet(void) { gpio_put(PIN_CS, 1); }
void AD5940_RstClr(void) { gpio_put(PIN_RST, 0); }
void AD5940_RstSet(void) { gpio_put(PIN_RST, 1); }
void AD5940_Delay10us(uint32_t time) { sleep_us(time * 10); }
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer,
unsigned char *pRecvBuff, unsigned long length) {
spi_write_read_blocking(spi0, pSendBuffer, pRecvBuff, length);
}
uint32_t AD5940_GetMCUIntFlag(void) { return (gpio_get(PIN_INT) == 0); }
uint32_t AD5940_ClrMCUIntFlag(void) { return 1; }
uint32_t AD5940_MCUResourceInit(void *pCfg) { return 0; }
void AD5940_MCUGpioWrite(uint32_t data) { (void)data; }
uint32_t AD5940_MCUGpioRead(uint32_t pin) {
(void)pin;
return 0;
}
void AD5940_MCUGpioCtrl(uint32_t pin, BoolFlag enable) {
(void)pin;
(void)enable;
}

35
rp2040port.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef _RP2040_PORT_H_
#define _RP2040_PORT_H_
#include "ad5940.h"
#include "hardware/gpio.h"
#include "hardware/spi.h"
#include "pico/stdlib.h"
// Hardware Definitions
#define PIN_MISO 0
#define PIN_CS 1
#define PIN_SCK 2
#define PIN_MOSI 3
#define PIN_RST 9
#define PIN_INT 29
// Function Prototypes
void setup_pins(void);
void AD5940_CsClr(void);
void AD5940_CsSet(void);
void AD5940_RstClr(void);
void AD5940_RstSet(void);
void AD5940_Delay10us(uint32_t time);
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer,
unsigned char *pRecvBuff, unsigned long length);
uint32_t AD5940_GetMCUIntFlag(void);
uint32_t AD5940_ClrMCUIntFlag(void);
uint32_t AD5940_MCUResourceInit(void *pCfg);
// These are stubs in the original platform file, keeping them here as well
void AD5940_MCUGpioWrite(uint32_t data);
uint32_t AD5940_MCUGpioRead(uint32_t pin);
void AD5940_MCUGpioCtrl(uint32_t pin, BoolFlag enable);
#endif /* _RP2040_PORT_H_ */

181
test/AD5940Main.c Normal file
View File

@ -0,0 +1,181 @@
/*!
*****************************************************************************
@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 = 100.0;
pImpedanceCfg->SinFreq = 60000.0;
pImpedanceCfg->FifoThresh = 4;
/* Set switch matrix to onboard(EVAL-AD5940ELECZ) dummy sensor. */
/* Note the RCAL0 resistor is 10kOhm. */
/* MODIFIED: Using AIN2/AIN3 for voltage sense as per user setup (CE0, SE0,
* AIN2, AIN3) */
pImpedanceCfg->DswitchSel = SWD_CE0;
pImpedanceCfg->PswitchSel = SWP_AIN2;
pImpedanceCfg->NswitchSel = SWN_AIN3;
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_1K;
/* Disable Hardware Sweep */
pImpedanceCfg->SweepCfg.SweepEn = bFALSE;
}
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. */
AppIMPCfg_Type *pImpedanceCfg;
AppIMPGetCfg(&pImpedanceCfg);
printf("\n--- EIS Configuration ---\n");
printf("RcalVal: %.2f Ohm\n", pImpedanceCfg->RcalVal);
printf("BiasVolt: %.2f mV\n", pImpedanceCfg->BiasVolt);
printf("HstiaRtiaSel: 0x%02X\n", pImpedanceCfg->HstiaRtiaSel);
printf("AC Volts PP: %.2f mV\n", pImpedanceCfg->DacVoltPP);
printf("-------------------------\n\n");
/* Calculate Sweep Points */
float start_freq = 100.0f;
float stop_freq = 100000.0f;
int num_points = 101;
// float log_step = pow(stop_freq / start_freq, 1.0 / (num_points - 1));
float log_step = 1.0715193f; // 1000^(1/100)
float curr_freq = start_freq;
printf("Starting Software Sweep: %.2f Hz to %.2f Hz, %d points\n", start_freq,
stop_freq, num_points);
for (int i = 0; i < num_points; i++) {
uint32_t temp;
/* Update Frequency */
pImpedanceCfg->SinFreq = curr_freq;
/* Re-Initialize App to apply new frequency (this regenerates sequences) */
AppIMPInit(AppBuff, APPBUFF_SIZE);
/* Calibrate RTIA at this frequency */
AppIMPRtiaCal();
/* Measure Impedance */
AppIMPCtrl(IMPCTRL_START, 0);
/* Wait for result */
while (1) {
if (AD5940_GetMCUIntFlag()) {
AD5940_ClrMCUIntFlag();
temp = APPBUFF_SIZE;
AppIMPISR(AppBuff, &temp);
ImpedanceShowResult(AppBuff, temp);
break; // Done with this point
}
}
/* Next Frequency */
curr_freq *= log_step;
}
printf("Sweep Completed.\n");
while (1)
; // Stop
}

37
test/CMakeLists.txt Normal file
View File

@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(TestImpedance C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
add_executable(TestImpedance
main.c
AD5940Main.c
Impedance.c
ad5940.c
rp2040port.c
)
target_compile_definitions(TestImpedance PRIVATE CHIPSEL_594X)
target_include_directories(TestImpedance PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(TestImpedance
pico_stdlib
hardware_spi
hardware_gpio
hardware_dma
hardware_irq
hardware_vreg
hardware_clocks
m
)
pico_enable_stdio_usb(TestImpedance 1)
pico_enable_stdio_uart(TestImpedance 0)
pico_add_extra_outputs(TestImpedance)

660
test/Impedance.c Normal file
View File

@ -0,0 +1,660 @@
/*!
*****************************************************************************
@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 "Impedance.h"
#include "ad5940.h"
#include "math.h"
#include "string.h"
#include <stdio.h>
/* Default LPDAC resolution(2.5V internal reference). */
#define DAC12BITVOLT_1LSB (2200.0f / 4095) // mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB * 64) // mV
static uint32_t const HpRtiaTable[] = {200, 1000, 5000, 10000, 20000,
40000, 80000, 160000, 0};
static float ResRtiaCal = 0.0f;
/*
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_HP,
.HstiaRtiaSel = HSTIARTIA_1K,
.ExcitBufGain = EXCITBUFGAIN_2,
.HsDacGain = HSDACGAIN_1,
.HsDacUpdateRate = 7,
.DacVoltPP = 800.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);
/* 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_SEQGenInsert(SEQ_WAIT(WaitClks));
// 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_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 */
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;
}
AD5940Err AppIMPRtiaCal(void) {
HSRTIACal_Type hs_cal;
fImpPol_Type res;
AD5940Err error;
hs_cal.fFreq = AppIMPCfg.SinFreq;
hs_cal.fRcal = AppIMPCfg.RcalVal;
hs_cal.SysClkFreq = AppIMPCfg.SysClkFreq;
hs_cal.AdcClkFreq = AppIMPCfg.AdcClkFreq;
hs_cal.HsTiaCfg.DiodeClose = bFALSE;
if (AppIMPCfg.BiasVolt != 0.0f)
hs_cal.HsTiaCfg.HstiaBias = HSTIABIAS_VZERO0;
else
hs_cal.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hs_cal.HsTiaCfg.HstiaCtia = 31;
hs_cal.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hs_cal.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hs_cal.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel;
hs_cal.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
hs_cal.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
hs_cal.DftCfg.DftNum = AppIMPCfg.DftNum;
hs_cal.DftCfg.DftSrc = AppIMPCfg.DftSrc;
hs_cal.DftCfg.HanWinEn = AppIMPCfg.HanWinEn;
hs_cal.bPolarResult = bTRUE;
hs_cal.bPolarResult = bTRUE;
error = AD5940_HSRtiaCal(&hs_cal, &res);
if (error == AD5940ERR_OK) {
ResRtiaCal = res.Magnitude;
printf("Calibrated RTIA: %f Ohm, Phase: %f\n", res.Magnitude, res.Phase);
} else
printf("RTIA Calibration Failed: %d\n", error);
return error;
}
/* 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);
/* Perform RTIA Calibration */
AppIMPRtiaCal();
/* 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);
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);
}
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;
// DEBUG: Print raw magnitudes and Rcal used
if (i == 0) {
uint32_t RtiaSel = AppIMPCfg.HstiaRtiaSel;
float ExpectedRtia = (float)HpRtiaTable[RtiaSel];
printf("\n--- RTIA Calibration Info ---\n");
printf("Selected RTIA Index: %d\n", RtiaSel);
printf("Expected RTIA: %.2f Ohm\n", ExpectedRtia);
printf("Calibrated RTIA: %f Ohm\n", ResRtiaCal);
printf("-----------------------------\n");
}
printf("DEBUG: RcalMag: %f, DutMag: %f, RcalVal: %f, RTIA_Cal: %f\n",
RcalMag, (RcalMag * AppIMPCfg.RcalVal) / RzMag, AppIMPCfg.RcalVal,
ResRtiaCal);
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;
}

104
test/Impedance.h Normal file
View File

@ -0,0 +1,104 @@
/*!
*****************************************************************************
@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 "math.h"
#include "string.h"
#include <stdio.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
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);
AD5940Err AppIMPRtiaCal(void);
#endif

28
test/Makefile Normal file
View File

@ -0,0 +1,28 @@
# Name of your build directory
BUILD_DIR = build
# Name of the output file (Must match what is in CMakeLists.txt)
TARGET = TestImpedance
# Default target: Build the project
all: $(BUILD_DIR)/Makefile
@$(MAKE) -C $(BUILD_DIR)
# Ensure the build directory exists and run CMake if needed
$(BUILD_DIR)/Makefile: CMakeLists.txt
@mkdir -p $(BUILD_DIR)
@cd $(BUILD_DIR) && cmake ..
# Clean up the build directory
clean:
@rm -rf $(BUILD_DIR)
# Helper to automatically flash (Mac specific path)
flash: all
@echo "Waiting for RPI-RP2 volume..."
@while [ ! -d /Volumes/RPI-RP2 ]; do sleep 0.1; done
@echo "Flashing..."
@cp $(BUILD_DIR)/$(TARGET).uf2 /Volumes/RPI-RP2/
@echo "Done."
.PHONY: all clean flash

4422
test/ad5940.c Normal file

File diff suppressed because it is too large Load Diff

6242
test/ad5940.h Normal file

File diff suppressed because it is too large Load Diff

18
test/main.c Normal file
View File

@ -0,0 +1,18 @@
#include "pico/stdlib.h"
#include "rp2040port.h"
#include <stdio.h>
extern void AD5940_Main(void);
int main(void) {
stdio_init_all();
setup_pins();
// Allow some time for USB to connect if needed, but don't block forever
sleep_ms(2000);
printf("Starting AD5940 Test...\n");
AD5940_Main();
return 0;
}

59
test/rp2040port.c Normal file
View File

@ -0,0 +1,59 @@
#include "rp2040port.h"
// --- Hardware Setup ---
void setup_pins(void) {
// SPI Initialisation. Using SPI0 at 16MHz.
spi_init(spi0, 16000000);
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
// Chip Select
gpio_init(PIN_CS);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_put(PIN_CS, 1);
// Reset Pin
gpio_init(PIN_RST);
gpio_set_dir(PIN_RST, GPIO_OUT);
gpio_put(PIN_RST, 1);
// Interrupt Pin
gpio_init(PIN_INT);
gpio_set_dir(PIN_INT, GPIO_IN);
gpio_pull_up(PIN_INT);
}
// --- Platform Interface Implementation ---
void AD5940_CsClr(void) { gpio_put(PIN_CS, 0); }
void AD5940_CsSet(void) { gpio_put(PIN_CS, 1); }
void AD5940_RstClr(void) { gpio_put(PIN_RST, 0); }
void AD5940_RstSet(void) { gpio_put(PIN_RST, 1); }
void AD5940_Delay10us(uint32_t time) { sleep_us(time * 10); }
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer,
unsigned char *pRecvBuff, unsigned long length) {
spi_write_read_blocking(spi0, pSendBuffer, pRecvBuff, length);
}
uint32_t AD5940_GetMCUIntFlag(void) { return (gpio_get(PIN_INT) == 0); }
uint32_t AD5940_ClrMCUIntFlag(void) { return 1; }
uint32_t AD5940_MCUResourceInit(void *pCfg) { return 0; }
void AD5940_MCUGpioWrite(uint32_t data) { (void)data; }
uint32_t AD5940_MCUGpioRead(uint32_t pin) {
(void)pin;
return 0;
}
void AD5940_MCUGpioCtrl(uint32_t pin, BoolFlag enable) {
(void)pin;
(void)enable;
}

34
test/rp2040port.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef _RP2040_PORT_H_
#define _RP2040_PORT_H_
#include "ad5940.h"
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "hardware/gpio.h"
// Hardware Definitions
#define PIN_MISO 0
#define PIN_CS 1
#define PIN_SCK 2
#define PIN_MOSI 3
#define PIN_RST 9
#define PIN_INT 29
// Function Prototypes
void setup_pins(void);
void AD5940_CsClr(void);
void AD5940_CsSet(void);
void AD5940_RstClr(void);
void AD5940_RstSet(void);
void AD5940_Delay10us(uint32_t time);
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuff, unsigned long length);
uint32_t AD5940_GetMCUIntFlag(void);
uint32_t AD5940_ClrMCUIntFlag(void);
uint32_t AD5940_MCUResourceInit(void *pCfg);
// These are stubs in the original platform file, keeping them here as well
void AD5940_MCUGpioWrite(uint32_t data);
uint32_t AD5940_MCUGpioRead(uint32_t pin);
void AD5940_MCUGpioCtrl(uint32_t pin, BoolFlag enable);
#endif /* _RP2040_PORT_H_ */