Compare commits
10 Commits
e271e9a046
...
5723e89a1a
| Author | SHA1 | Date |
|---|---|---|
|
|
5723e89a1a | |
|
|
b1427c57c8 | |
|
|
6fc616593b | |
|
|
ce6f8e6a46 | |
|
|
2945835eeb | |
|
|
3e78b4eb75 | |
|
|
6adf55bc47 | |
|
|
4266c4b672 | |
|
|
42871ed837 | |
|
|
52c6bef53b |
|
|
@ -390,7 +390,105 @@ The AD594x includes two 16-bit general-purpose timers, AGPT0 and AGPT1. These ti
|
|||
|---|---|---|---|
|
||||
| 0-15 | **MATCHVAL** | 0x0000FFFF | PWM Match Value |
|
||||
For data integrity tasks, the device includes a dedicated hardware accelerator.
|
||||
### 8.0 CRC Accelerator (AFECRC)
|
||||
|
||||
## 8.0 Switch Matrix & Electrode Connections (SWMAT)
|
||||
The AD5940 features a highly flexible switch matrix that routes external electrode signals (SE0, RE0, CE0, AINx) to internal blocks like the High-Speed DAC (Excitation), the ADC, and the TIA. The matrix is divided into four main blocks: **D** (DAC/Excitation), **P** (Positive Input), **N** (Negative Input), and **T** (TIA/Transimpedance).
|
||||
**8.1 Core Switch Registers**
|
||||
These registers provide direct control over every switch in the matrix.
|
||||
**AFE_SWCON**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
| **SWCON** | 0x0000200C | 0x0000FFFF | Master Switch Matrix Configuration |
|
||||
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|
||||
|---|---|---|---|
|
||||
| 12-15 | **TMUXCON** | 0x0000F000 | Control of T Switch MUX |
|
||||
| 8-11 | **NMUXCON** | 0x00000F00 | Control of N Switch MUX |
|
||||
| 4-7 | **PMUXCON** | 0x000000F0 | Control of P Switch MUX |
|
||||
| 0-3 | **DMUXCON** | 0x0000000F | Control of D Switch MUX |
|
||||
**AFE_DSWFULLCON (Excitation/DAC Path)**
|
||||
Controls switches connecting pins to the Excitation Loop (High-Speed DAC output).
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
| **DSWFULLCON** | 0x00002150 | 0x00000000 | D-Block Switch Configuration |
|
||||
| Bit Position(s) | Bitfield Name | Bitmask | Connection/Description |
|
||||
|---|---|---|---|
|
||||
| 7 | **D8** | 0x00000080 | Connects **SE0** to D-Node |
|
||||
| 6 | **D7** | 0x00000040 | Connects **CE0** to D-Node |
|
||||
| 5 | **D6** | 0x00000020 | Connects **RE0** to D-Node |
|
||||
| 4 | **D5** | 0x00000010 | Connects **AIN3** to D-Node |
|
||||
| 3 | **D4** | 0x00000008 | Connects **AIN2** to D-Node |
|
||||
| 2 | **D3** | 0x00000004 | Connects **AIN1** to D-Node |
|
||||
| 1 | **D2** | 0x00000002 | Connects **AIN0** to D-Node |
|
||||
| 0 | **DR0** | 0x00000001 | Connects **RCAL0** to D-Node |
|
||||
**AFE_PSWFULLCON (Positive Input Path)**
|
||||
Controls switches connecting pins to the Positive Input bus (ADC P-Input, etc.).
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
| **PSWFULLCON** | 0x00002154 | 0x00000000 | P-Block Switch Configuration |
|
||||
| Bit Position(s) | Bitfield Name | Bitmask | Connection/Description |
|
||||
|---|---|---|---|
|
||||
| 14 | **PL2** | 0x00004000 | Connects **CE0** to P-Node |
|
||||
| 13 | **PL** | 0x00002000 | Connects **SE0** to P-Node |
|
||||
| 11 | **P12** | 0x00000800 | Connects **DE0** to P-Node |
|
||||
| 10 | **P11** | 0x00000400 | Connects **RE0** to P-Node |
|
||||
| 4 | **P5** | 0x00000010 | Connects **AIN3** to P-Node |
|
||||
| 3 | **P4** | 0x00000008 | Connects **AIN2** to P-Node |
|
||||
| 2 | **P3** | 0x00000004 | Connects **AIN1** to P-Node |
|
||||
| 1 | **P2** | 0x00000002 | Connects **AIN0** to P-Node |
|
||||
| 0 | **PR0** | 0x00000001 | Connects **RCAL0** to P-Node |
|
||||
**AFE_NSWFULLCON (Negative Input Path)**
|
||||
Controls switches connecting pins to the Negative Input bus (ADC N-Input, TIA Input).
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
| **NSWFULLCON** | 0x00002158 | 0x00000000 | N-Block Switch Configuration |
|
||||
| Bit Position(s) | Bitfield Name | Bitmask | Connection/Description |
|
||||
|---|---|---|---|
|
||||
| 11 | **NL2** | 0x00000800 | Connects **CE0** to N-Node |
|
||||
| 10 | **NL** | 0x00000400 | Connects **SE0** to N-Node |
|
||||
| 8 | **N9** | 0x00000100 | Connects **DE0** to N-Node |
|
||||
| 7 | **N8** | 0x00000080 | Connects **RE0** to N-Node |
|
||||
| 4 | **N5** | 0x00000010 | Connects **AIN3** to N-Node |
|
||||
| 3 | **N4** | 0x00000008 | Connects **AIN2** to N-Node |
|
||||
| 2 | **N3** | 0x00000004 | Connects **AIN1** to N-Node |
|
||||
| 1 | **N2** | 0x00000002 | Connects **AIN0** to N-Node |
|
||||
| 0 | **NR1** | 0x00000200 | Connects **RCAL0** to N-Node |
|
||||
**AFE_TSWFULLCON (TIA Feedback Path)**
|
||||
Controls switches connecting pins primarily for TIA feedback loops.
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
| **TSWFULLCON** | 0x0000215C | 0x00000000 | T-Block Switch Configuration |
|
||||
| Bit Position(s) | Bitfield Name | Bitmask | Connection/Description |
|
||||
|---|---|---|---|
|
||||
| 10 | **T11** | 0x00000400 | **SE0** T-Switch (Often used for SE0-RE0 Short) |
|
||||
| 9 | **T10** | 0x00000200 | **DE0** T-Switch |
|
||||
| 8 | **T9** | 0x00000100 | **RE0** T-Switch |
|
||||
| 6 | **T7** | 0x00000040 | **AIN3** T-Switch |
|
||||
| 4 | **T5** | 0x00000010 | **AIN2** T-Switch |
|
||||
| 3 | **T4** | 0x00000008 | **AIN1** T-Switch |
|
||||
| 2 | **T3** | 0x00000004 | **AIN0** T-Switch |
|
||||
| 1 | **T2** | 0x00000002 | **RCAL0** T-Switch |
|
||||
**8.2 Shorting SE0 to RE0 (LPTIA Switch)**
|
||||
While the TSWFULLCON register contains bit **T11** (associated with SE0), the specific functionality to short **SE0** to **RE0** is explicitly defined as a macro configuration in the Low Power TIA Switch register. This closes internal switch **SW11**.
|
||||
**AFE_LPTIASW0**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
| **LPTIASW0** | 0x000020E4 | 0x00000000 | LPTIA Channel 0 Switch Config |
|
||||
**Key Configuration for SE0-RE0 Short:**
|
||||
* **Bitmask**: 0x00000800
|
||||
* **Enum Name**: ENUM_AFE_LPTIASWO_SESHORTRE
|
||||
* **Description**: Closes **SW11**. This physically shorts the Sense Electrode 0 (SE0) to the Reference Electrode 0 (RE0). This is commonly used in 2-wire measurement modes or specific sensor biasing configurations.
|
||||
|
||||
⠀**8.3 Electrode Pin Capability Summary**
|
||||
| **Pin Name** | **Can Source (DAC)?** | **Can Sink (TIA)?** | **Can Sense (ADC P/N)?** | **Relevant Switches** |
|
||||
|---|---|---|---|---|
|
||||
| **CE0** | Yes (via D7) | Yes (via NL2) | Yes (via PL2/NL2) | D7, PL2, NL2 |
|
||||
| **RE0** | Yes (via D6) | Yes (via N8) | Yes (via P11/N8) | D6, P11, N8, T9 |
|
||||
| **SE0** | Yes (via D8) | Yes (via NL) | Yes (via PL/NL) | D8, PL, NL, T11 |
|
||||
| **AIN0-3** | Yes (via D2-D5) | Yes (via N2-N5) | Yes (via P2-P5) | D2-D5, P2-P5, N2-N5 |
|
||||
| **RCAL0** | Yes (via DR0) | Yes (via NR1) | Yes (via PR0) | DR0, PR0, NR1 |
|
||||
|
||||
|
||||
### 9.0 CRC Accelerator (AFECRC)
|
||||
The AFECRC module is a dedicated hardware accelerator for calculating Cyclic Redundancy Checks (CRC). This feature is essential for ensuring data integrity, allowing for fast and efficient error-checking of memory contents (like the sequencer command SRAM) or communication data without burdening the host processor.
|
||||
**AFECRC_CTL**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|
|
@ -460,9 +558,9 @@ The AFECRC module is a dedicated hardware accelerator for calculating Cyclic Red
|
|||
|---|---|---|---|
|
||||
| 0 | **CRC_ERR_ST** | 0x00000001 | CRC Error Interrupt Status Bit |
|
||||
The next section details the main Analog Front End, which integrates all measurement capabilities.
|
||||
### 9.0 Analog Front End Core (AFE)
|
||||
### 10.0 Analog Front End Core (AFE)
|
||||
The AFE block is the heart of the AD594x, integrating all the high-performance analog and digital components required for advanced electrochemical and bioimpedance measurements. This comprehensive module contains the high-speed and low-power Digital-to-Analog Converters (DACs) and Transimpedance Amplifiers (TIAs), the ADC and its associated digital filters, the powerful command sequencer with its data and command FIFOs, and the flexible switch matrix that routes signals between the analog blocks and external pins.
|
||||
**9.1 Core and Sequencer Configuration**
|
||||
**10.1 Core and Sequencer Configuration**
|
||||
**AFE_AFECON**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
|
|
@ -523,7 +621,7 @@ The AFE block is the heart of the AD594x, integrating all the high-performance a
|
|||
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|
||||
|---|---|---|---|
|
||||
| 0-29 | **TIMEOUT** | 0x3FFFFFFF | Current Value of the Sequencer Timeout Counter. |
|
||||
**9.2 Waveform Generator**
|
||||
**10.2 Waveform Generator**
|
||||
**AFE_WGCON**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
|
|
@ -571,7 +669,7 @@ The AFE block is the heart of the AD594x, integrating all the high-performance a
|
|||
| **WGSLOPE1** | 0x00002024 | 0x00000000 | Waveform Generator - Trapezoid Slope 1 Time |
|
||||
| **WGDELAY2** | 0x00002028 | 0x00000000 | Waveform Generator - Trapezoid Delay 2 Time |
|
||||
| **WGSLOPE2** | 0x0000202C | 0x00000000 | Waveform Generator - Trapezoid Slope 2 Time |
|
||||
**9.3 ADC, Filters, and Data Path**
|
||||
**10.3 ADC, Filters, and Data Path**
|
||||
**AFE_ADCFILTERCON**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
|
|
@ -613,7 +711,7 @@ The AFE block is the heart of the AD594x, integrating all the high-performance a
|
|||
| **TEMPSENSDAT** | 0x00002084 | 0x00000000 | Temperature Sensor Result |
|
||||
| **DATAFIFORD** | 0x0000206C | 0x00000000 | Data FIFO Read |
|
||||
| **CMDFIFOWRITE** | 0x00002070 | 0x00000000 | Command FIFO Write |
|
||||
**9.4 TIA and DAC Configuration**
|
||||
**10.4 TIA and DAC Configuration**
|
||||
**High-Speed DAC and TIA**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
|
|
@ -642,7 +740,7 @@ The AFE block is the heart of the AD594x, integrating all the high-performance a
|
|||
| 13-15 | **TIARF** | 0x0000E000 | Set LPF Resistor | 0x00000000 (DISCONRF): Disconnect<br>0x00002000 (BYPRF): Bypass<br>0x00004000 (RF20K): 20 kΩ<br>0x00006000 (RF100K): 100 kΩ<br>0x00008000 (RF200K): 200 kΩ<br>0x0000A000 (RF400K): 400 kΩ<br>0x0000C000 (RF600K): 600 kΩ<br>0x0000E000 (RF1MOHM): 1 MΩ |
|
||||
| 1 | **PAPDEN** | 0x00000002 | PA Power Down | |
|
||||
| 0 | **TIAPDEN** | 0x00000001 | TIA Power Down | |
|
||||
**9.5 Switch Matrix**
|
||||
**10.5 Switch Matrix**
|
||||
**AFE_SWCON**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
|
|
@ -665,7 +763,7 @@ The AFE block is the heart of the AD594x, integrating all the high-performance a
|
|||
| **PSWSTA** | 0x000021B4 | 0x00006000 | Switch Matrix Status (P) |
|
||||
| **NSWSTA** | 0x000021B8 | 0x00000C00 | Switch Matrix Status (N) |
|
||||
| **TSWSTA** | 0x000021BC | 0x00000000 | Switch Matrix Status (T) |
|
||||
**9.6 Power Management and Calibration**
|
||||
**10.6 Power Management and Calibration**
|
||||
| Register Name | Address | Reset Value | Description |
|
||||
|---|---|---|---|
|
||||
| **PMBW** | 0x000022F0 | 0x00088800 | Power Mode Configuration |
|
||||
|
|
@ -677,11 +775,11 @@ The AFE block is the heart of the AD594x, integrating all the high-performance a
|
|||
| **DACOFFSET** | 0x00002268 | 0x00000000 | DAC Offset with Attenuator Disabled (LP Mode) |
|
||||
| **DACGAIN** | 0x00002260 | 0x00000800 | DACGAIN |
|
||||
This comprehensive set of registers enables fine-grained control over the AD594x's powerful analog capabilities. To illustrate how these are used in practice, the following section provides a functional example.
|
||||
### 10.0 Functional Example: Low Frequency Oscillator (LFOSC) Calibration
|
||||
### 11.0 Functional Example: Low Frequency Oscillator (LFOSC) Calibration
|
||||
This section provides a practical, high-level example of how the AD594x's features, particularly the sequencer and timers, are used to perform a critical task: calibrating the internal low-frequency oscillator (LFOSC).
|
||||
**10.1 Overview**
|
||||
**11.1 Overview**
|
||||
The LFOSC is the clock source for the sleep/wakeup timer, which controls the measurement frequency of the AD5940 (i.e., how often it wakes up to run a measurement sequence). For applications that require a highly accurate and repeatable measurement sample rate, the LFOSC must be calibrated against a more precise, high-frequency clock source, such as an external 16 MHz crystal. The calibration process uses two measurement sequences to accurately measure the LFOSC period.
|
||||
**10.2 Calibration Steps**
|
||||
**11.2 Calibration Steps**
|
||||
The calibration process involves the following sequence of operations:
|
||||
**1** **Configure Sequence A:** A sequence is created containing a single command, SEQ_TOUT(0x3fffffff), which starts the high-resolution sequencer timeout counter. This sequence is written to a specific location in the AFE's command SRAM (e.g., Sequence ID 0).
|
||||
**2** **Configure Sequence B:** A second sequence is created with a single SEQ_STOP() command. This command halts the sequencer and generates an END_SEQ interrupt, signaling the host microcontroller that a measurement period is complete. This sequence is written to another SRAM location (e.g., Sequence ID 1).
|
||||
|
|
@ -691,7 +789,7 @@ The calibration process involves the following sequence of operations:
|
|||
**6** **Measure Read Latency:** Sequence B is reconfigured to reset the timeout counter before generating the END_SEQ interrupt. It is then run again. This time, the value read back by the host (TimerCount2) represents only the time it takes to read the register.
|
||||
**7** **Calculate Frequency:** The final LFOSC frequency is calculated using the difference between TimerCount and TimerCount2, along with the known system clock frequency.
|
||||
|
||||
⠀**10.3 Frequency Calculation**
|
||||
⠀**11.3 Frequency Calculation**
|
||||
***Editor's Note:*** *The formula presented in the original source documentation for this calculation appears to be erroneous, simplifying to a physically impossible result. The formula below has been corrected to reflect the logical intent of the calibration procedure.*
|
||||
The frequency of the low-frequency oscillator is calculated by determining how many system clock cycles occur during a known number of LFOSC cycles (CalDuration_in_ticks). The corrected formula is:
|
||||
### Frequency = (CalDuration_in_ticks * SystemClkFreq) / (TimerCount - TimerCount2)
|
||||
|
|
@ -703,9 +801,11 @@ Where:
|
|||
* **TimerCount2**: The value read from the **SEQTIMEOUT** register after the second (latency) measurement.
|
||||
* **(TimerCount - TimerCount2)**: This difference represents the total number of system clock cycles that elapsed during the CalDuration period.
|
||||
|
||||
⠀**10.4 SDK Implementation Note**
|
||||
⠀**11.4 SDK Implementation Note**
|
||||
The AD5940 Software Development Kit (SDK) provides a ready-to-use function, AD5940_LFOSCMeasure(), which encapsulates this entire calibration logic. This function simplifies the process for the developer and takes key input parameters to control the measurement:
|
||||
* **CalDuration**: Sets the length of the calibration routine (a value of 1000 ms is advisable for accuracy).
|
||||
* **SystemClkFreq**: Sets the system clock frequency, which should be 16 MHz for best results.
|
||||
|
||||
⠀By performing this calibration, applications can achieve a high level of accuracy in their measurement sampling frequency, which is critical for time-sensitive electrochemical analysis.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ build*
|
|||
*.png
|
||||
icons/
|
||||
*.pdf
|
||||
*.txt
|
||||
examples/
|
||||
|
||||
build/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
# Enable auto-env through the sdkman_auto_env config
|
||||
# Add key=value pairs of SDKs to use below
|
||||
java=17-homebrew
|
||||
gradle=9.2.1
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
// File: AD5940_Platform.c
|
||||
#include "App_Common.h"
|
||||
#include "hardware/spi.h"
|
||||
#include "hardware/gpio.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);
|
||||
}
|
||||
|
||||
int32_t AD5940PlatformCfg(void) {
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void SystemReset(void) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void AmperometricShowResult(float *pData, uint32_t DataCount) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
// File: Amperometric.c
|
||||
#include "Amperometric.h"
|
||||
|
||||
#define AD5940ERR_STOP 10
|
||||
|
||||
/*
|
||||
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 = 100.0, /* RCAL = 100 Ohms */
|
||||
.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*/
|
||||
.ShortRe0Se0 = bFALSE,
|
||||
};
|
||||
|
||||
/**
|
||||
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);
|
||||
}
|
||||
|
||||
// Apply Short Option
|
||||
if(AppAMPCfg.ShortRe0Se0) {
|
||||
lp_loop.LpAmpCfg.LpTiaSW |= LPTIASW(11);
|
||||
}
|
||||
|
||||
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_STOP; // Return STOP code
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
AD5940Err status = 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;
|
||||
|
||||
if (status == AD5940ERR_STOP) return AD5940ERR_STOP;
|
||||
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;
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// File: Amperometric.h
|
||||
#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
|
||||
|
||||
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 */
|
||||
BoolFlag ShortRe0Se0; /* Short RE0 to SE0 */
|
||||
/* 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
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
// File: App_Common.h
|
||||
#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 "RampTest.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
|
||||
|
||||
#define APPBUFF_SIZE 512
|
||||
#define AD5940ERR_STOP 10
|
||||
|
||||
#ifndef LPTIARF_BYPASS
|
||||
#define LPTIARF_BYPASS 0x2000
|
||||
#endif
|
||||
|
||||
// --- Application State Enums ---
|
||||
typedef enum {
|
||||
MODE_IDLE,
|
||||
MODE_IMPEDANCE,
|
||||
MODE_AMPEROMETRIC,
|
||||
MODE_RAMP
|
||||
} AppMode;
|
||||
|
||||
// --- Global Variables ---
|
||||
extern uint32_t AppBuff[APPBUFF_SIZE];
|
||||
extern AppMode CurrentMode;
|
||||
extern float LFOSCFreq;
|
||||
extern uint32_t g_AmpIndex;
|
||||
extern uint32_t g_RampIndex;
|
||||
|
||||
// Configuration Globals
|
||||
extern uint32_t ConfigLptiaVal;
|
||||
extern uint32_t ConfigHstiaVal;
|
||||
extern uint32_t CurrentLpTiaRf;
|
||||
extern uint32_t ConfigRLoad;
|
||||
extern float CalibratedLptiaVal;
|
||||
extern float CalibratedHstiaVal;
|
||||
extern BoolFlag GlobalShortRe0Se0;
|
||||
|
||||
// --- Function Prototypes ---
|
||||
|
||||
// From AD5940_Platform.c
|
||||
void setup_pins(void);
|
||||
int32_t AD5940PlatformCfg(void);
|
||||
void SystemReset(void);
|
||||
uint32_t GetHSTIARtia(uint32_t val);
|
||||
uint32_t GetLPTIARtia(uint32_t val);
|
||||
uint32_t GetLPTIARload(uint32_t val);
|
||||
void ImpedanceShowResult(uint32_t *pData, uint32_t DataCount);
|
||||
void AmperometricShowResult(float *pData, uint32_t DataCount);
|
||||
void RampShowResult(float *pData, uint32_t DataCount);
|
||||
|
||||
// From Measurement_Core.c
|
||||
void AD5940ImpedanceStructInit(void);
|
||||
void AD5940AMPStructInit(void);
|
||||
void AD5940RampStructInit(void);
|
||||
void Config_LPLOOP(float bias_mv);
|
||||
void Calibrate_HSDAC(float freq);
|
||||
void Configure_Filters(float freq);
|
||||
void Do_WaveGen(float freq);
|
||||
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_Amperometric(float bias_mv);
|
||||
void Routine_LSV(float start_mv, float end_mv, int steps, int duration_ms);
|
||||
void Routine_CalibrateSystem(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -15,6 +15,12 @@ add_executable(EIS
|
|||
main.c
|
||||
ad5940.c
|
||||
Impedance.c
|
||||
Amperometric.c
|
||||
RampTest.c
|
||||
Reset.c
|
||||
Measurement_Core.c
|
||||
Measurement_Routines.c
|
||||
AD5940_Platform.c
|
||||
)
|
||||
|
||||
target_compile_definitions(EIS PRIVATE CHIPSEL_594X)
|
||||
|
|
|
|||
180
Impedance.c
180
Impedance.c
|
|
@ -5,9 +5,14 @@
|
|||
#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
|
||||
#define AD5940ERR_STOP 10
|
||||
|
||||
/* Default LPDAC resolution (2.5V internal reference) */
|
||||
#define DAC12BITVOLT_1LSB (2200.0f/4095) // mV
|
||||
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) // mV
|
||||
|
||||
/* Forward declaration */
|
||||
AD5940Err AppIMPCheckFreq(float freq);
|
||||
|
||||
AppIMPCfg_Type AppIMPCfg =
|
||||
{
|
||||
|
|
@ -18,13 +23,14 @@ AppIMPCfg_Type AppIMPCfg =
|
|||
.SeqStartAddrCal = 0,
|
||||
.MaxSeqLenCal = 0,
|
||||
|
||||
.ImpODR = 20.0, /* 20.0 Hz = 50ms period */
|
||||
.NumOfData = -1,
|
||||
.ImpODR = 20.0, /* Output Data Rate: 20.0 Hz */
|
||||
.NumOfData = 101, /* Default to 101 points (matches default sweep) */
|
||||
.RealDataCount = -1,
|
||||
.SysClkFreq = 16000000.0,
|
||||
.WuptClkFreq = 32000.0,
|
||||
.WuptClkFreq = 32000.0, /* Low Frequency Oscillator (LFO) typically 32kHz */
|
||||
.AdcClkFreq = 16000000.0,
|
||||
.RcalVal = 10000.0,
|
||||
.RtiaVal = 200.0,
|
||||
.RcalVal = 100.0, /* Calibration Resistor Value (Ohms) */
|
||||
.RtiaVal = 200.0, /* TIA Gain Resistor Value (Ohms) */
|
||||
|
||||
.DswitchSel = SWD_CE0,
|
||||
.PswitchSel = SWP_CE0,
|
||||
|
|
@ -38,14 +44,14 @@ AppIMPCfg_Type AppIMPCfg =
|
|||
.ExcitBufGain = EXCITBUFGAIN_0P25,
|
||||
.HsDacGain = HSDACGAIN_0P2,
|
||||
.HsDacUpdateRate = 7,
|
||||
.DacVoltPP = 600.0,
|
||||
.BiasVolt = -0.0f,
|
||||
.DacVoltPP = 600.0, /* Excitation Amplitude (mV peak-to-peak) */
|
||||
.BiasVolt = -0.0f, /* DC Bias Voltage */
|
||||
|
||||
.SinFreq = 1000.0,
|
||||
.SinFreq = 1000.0, /* Fixed Frequency (Hz) */
|
||||
|
||||
.DftNum = DFTNUM_16384,
|
||||
.DftNum = DFTNUM_16384, /* DFT Point Count */
|
||||
.DftSrc = DFTSRC_SINC3,
|
||||
.HanWinEn = bTRUE,
|
||||
.HanWinEn = bTRUE, /* Hanning Window for spectral leakage reduction */
|
||||
|
||||
.AdcPgaGain = ADCPGA_1P5,
|
||||
.ADCSinc3Osr = ADCSINC3OSR_2,
|
||||
|
|
@ -53,34 +59,69 @@ AppIMPCfg_Type AppIMPCfg =
|
|||
|
||||
.ADCAvgNum = ADCAVGNUM_16,
|
||||
|
||||
/* Default Sweep: 1kHz to 100kHz, 50 Points Per Decade (Log) */
|
||||
/* Decades = log10(100k) - log10(1k) = 2. Points = 2 * 50 + 1 = 101 */
|
||||
.SweepCfg.SweepEn = bTRUE,
|
||||
.SweepCfg.SweepStart = 1000,
|
||||
.SweepCfg.SweepStart = 1000.0,
|
||||
.SweepCfg.SweepStop = 100000.0,
|
||||
.SweepCfg.SweepPoints = 101,
|
||||
.SweepCfg.SweepLog = bFALSE,
|
||||
.SweepCfg.SweepLog = bTRUE,
|
||||
.SweepCfg.SweepIndex = 0,
|
||||
|
||||
.FifoThresh = 6, /* 3 measurements * 2 (Real/Imag) = 6 words */
|
||||
.FifoThresh = 6, /* Threshold: 3 measurements * 2 (Real/Imag) = 6 words */
|
||||
.IMPInited = bFALSE,
|
||||
.StopRequired = bFALSE,
|
||||
.ShortRe0Se0 = bFALSE,
|
||||
};
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* Helper Functions */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* @brief Configures the Impedance Sweep parameters.
|
||||
* @param start Start Frequency in Hz
|
||||
* @param stop Stop Frequency in Hz
|
||||
* @param ppd Points Per Decade (Resolution)
|
||||
*/
|
||||
void AppIMPConfigureSweep(float start, float stop, float ppd) {
|
||||
if (start <= 0 || stop <= 0 || ppd <= 0) return;
|
||||
|
||||
AppIMPCfg.SweepCfg.SweepEn = bTRUE;
|
||||
AppIMPCfg.SweepCfg.SweepStart = start;
|
||||
AppIMPCfg.SweepCfg.SweepStop = stop;
|
||||
AppIMPCfg.SweepCfg.SweepLog = bTRUE;
|
||||
|
||||
// Calculate total points based on decades and PPD
|
||||
// Formula: Points = (Decades * PPD) + 1
|
||||
float decades = log10f(stop) - log10f(start);
|
||||
if (decades < 0) decades = -decades; // Handle sweep down if needed
|
||||
|
||||
uint32_t points = (uint32_t)(decades * ppd) + 1;
|
||||
AppIMPCfg.SweepCfg.SweepPoints = points;
|
||||
|
||||
// Set NumOfData to stop the sequencer automatically after the sweep
|
||||
AppIMPCfg.NumOfData = points;
|
||||
|
||||
// Reset Sweep State
|
||||
AppIMPCfg.FifoDataCount = 0;
|
||||
AppIMPCfg.SweepCfg.SweepIndex = 0;
|
||||
AppIMPCfg.SweepCurrFreq = start;
|
||||
AppIMPCfg.SweepNextFreq = start;
|
||||
}
|
||||
|
||||
void AppIMPCleanup(void) {
|
||||
// Ensure chip is awake before sending commands
|
||||
if(AD5940_WakeUp(10) > 10) return;
|
||||
|
||||
// 1. Stop Sequencer and Wakeup Timer
|
||||
// Stop Sequencer and Wakeup Timer
|
||||
AD5940_WUPTCtrl(bFALSE);
|
||||
AD5940_SEQCtrlS(bFALSE);
|
||||
|
||||
// 2. Stop any active conversions/WG, but KEEP Reference/LDOs ON
|
||||
// Stop active conversions and Waveform Generator, keep Reference/LDOs on
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG, bFALSE);
|
||||
|
||||
// 3. Reset FIFO
|
||||
// Reset FIFO configuration
|
||||
FIFOCfg_Type fifo_cfg;
|
||||
fifo_cfg.FIFOEn = bFALSE;
|
||||
AD5940_FIFOCfg(&fifo_cfg);
|
||||
|
|
@ -92,7 +133,7 @@ void AppIMPCleanup(void) {
|
|||
fifo_cfg.FIFOThresh = 6;
|
||||
AD5940_FIFOCfg(&fifo_cfg);
|
||||
|
||||
// 4. Clear Interrupts
|
||||
// Clear all interrupt flags
|
||||
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +144,7 @@ void AppIMPCalibrateLFO(void) {
|
|||
cal_cfg.SystemClkFreq = 16000000.0;
|
||||
|
||||
float freq;
|
||||
// Use SDK function to measure LFOSC
|
||||
// Measure LFOSC frequency to calibrate Wakeup Timer
|
||||
if(AD5940_LFOSCMeasure(&cal_cfg, &freq) == AD5940ERR_OK) {
|
||||
AppIMPCfg.WuptClkFreq = freq;
|
||||
}
|
||||
|
|
@ -127,7 +168,7 @@ int32_t AppIMPCtrl(uint32_t Command, void *pPara)
|
|||
{
|
||||
case IMPCTRL_START:
|
||||
{
|
||||
// Unified Start Logic: Always use Manual Trigger + ENDSEQ Interrupt
|
||||
// Configure interrupts and trigger the first sequence manually
|
||||
AD5940_WUPTCtrl(bFALSE);
|
||||
|
||||
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ENDSEQ, bTRUE);
|
||||
|
|
@ -137,7 +178,23 @@ int32_t AppIMPCtrl(uint32_t Command, void *pPara)
|
|||
AppIMPCfg.FifoDataCount = 0;
|
||||
AppIMPCfg.StopRequired = bFALSE;
|
||||
|
||||
// Trigger first sequence manually
|
||||
// Reset Sweep State for subsequent sweeps
|
||||
if(AppIMPCfg.SweepCfg.SweepEn)
|
||||
{
|
||||
AppIMPCfg.SweepCfg.SweepIndex = 0;
|
||||
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepCfg.SweepStart;
|
||||
AppIMPCfg.FreqofData = AppIMPCfg.SweepCfg.SweepStart;
|
||||
|
||||
// Calculate next frequency immediately so the ISR has the correct 'next' value
|
||||
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
|
||||
|
||||
// Reset Hardware WG Frequency to Start Frequency (it was left at Stop Freq)
|
||||
AD5940_WGFreqCtrlS(AppIMPCfg.SweepCurrFreq, AppIMPCfg.SysClkFreq);
|
||||
|
||||
// Reset SRAM Wait Times for the Start Frequency
|
||||
AppIMPCheckFreq(AppIMPCfg.SweepCurrFreq);
|
||||
}
|
||||
|
||||
AD5940_SEQMmrTrig(SEQID_0);
|
||||
break;
|
||||
}
|
||||
|
|
@ -188,6 +245,7 @@ float AppIMPGetCurrFreq(void)
|
|||
return AppIMPCfg.SinFreq;
|
||||
}
|
||||
|
||||
/* Generate Initialization Sequence */
|
||||
static AD5940Err AppIMPSeqCfgGen(void)
|
||||
{
|
||||
AD5940Err error = AD5940ERR_OK;
|
||||
|
|
@ -201,6 +259,7 @@ static AD5940Err AppIMPSeqCfgGen(void)
|
|||
AD5940_SEQGenCtrl(bTRUE);
|
||||
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
|
||||
|
||||
// Configure Reference System (High Power and Low Power Buffers)
|
||||
aferef_cfg.HpBandgapEn = bTRUE;
|
||||
aferef_cfg.Hp1V1BuffEn = bTRUE;
|
||||
aferef_cfg.Hp1V8BuffEn = bTRUE;
|
||||
|
|
@ -215,6 +274,7 @@ static AD5940Err AppIMPSeqCfgGen(void)
|
|||
aferef_cfg.LpRefBoostEn = bFALSE;
|
||||
AD5940_REFCfgS(&aferef_cfg);
|
||||
|
||||
// Configure High Speed Loop (DAC and TIA)
|
||||
HsLoopCfg.HsDacCfg.ExcitBufGain = AppIMPCfg.ExcitBufGain;
|
||||
HsLoopCfg.HsDacCfg.HsDacGain = AppIMPCfg.HsDacGain;
|
||||
HsLoopCfg.HsDacCfg.HsDacUpdateRate = AppIMPCfg.HsDacUpdateRate;
|
||||
|
|
@ -227,11 +287,13 @@ static AD5940Err AppIMPSeqCfgGen(void)
|
|||
HsLoopCfg.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel;
|
||||
HsLoopCfg.HsTiaCfg.ExtRtia = AppIMPCfg.ExtRtia;
|
||||
|
||||
// Configure Switch Matrix
|
||||
HsLoopCfg.SWMatCfg.Dswitch = AppIMPCfg.DswitchSel;
|
||||
HsLoopCfg.SWMatCfg.Pswitch = AppIMPCfg.PswitchSel;
|
||||
HsLoopCfg.SWMatCfg.Nswitch = AppIMPCfg.NswitchSel;
|
||||
HsLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA|AppIMPCfg.TswitchSel;
|
||||
|
||||
// Configure Waveform Generator (Sine Wave)
|
||||
HsLoopCfg.WgCfg.WgType = WGTYPE_SIN;
|
||||
HsLoopCfg.WgCfg.GainCalEn = bTRUE;
|
||||
HsLoopCfg.WgCfg.OffsetCalEn = bTRUE;
|
||||
|
|
@ -255,6 +317,17 @@ static AD5940Err AppIMPSeqCfgGen(void)
|
|||
HsLoopCfg.WgCfg.SinCfg.SinPhaseWord = 0;
|
||||
AD5940_HSLoopCfgS(&HsLoopCfg);
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Configure DSP and ADC
|
||||
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
|
||||
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;
|
||||
dsp_cfg.ADCBaseCfg.ADCPga = AppIMPCfg.AdcPgaGain;
|
||||
|
|
@ -275,6 +348,7 @@ static AD5940Err AppIMPSeqCfgGen(void)
|
|||
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg));
|
||||
AD5940_DSPCfgS(&dsp_cfg);
|
||||
|
||||
// Enable Power for AFE blocks
|
||||
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
|
||||
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
|
||||
AFECTRL_SINC2NOTCH, bTRUE);
|
||||
|
|
@ -296,6 +370,7 @@ static AD5940Err AppIMPSeqCfgGen(void)
|
|||
return AD5940ERR_OK;
|
||||
}
|
||||
|
||||
/* Generate Measurement Sequence */
|
||||
static AD5940Err AppIMPSeqMeasureGen(void)
|
||||
{
|
||||
AD5940Err error = AD5940ERR_OK;
|
||||
|
|
@ -305,6 +380,7 @@ static AD5940Err AppIMPSeqMeasureGen(void)
|
|||
SWMatrixCfg_Type sw_cfg;
|
||||
ClksCalInfo_Type clks_cal;
|
||||
|
||||
// Calculate settling time (WaitClks) based on DFT/Filter settings
|
||||
clks_cal.DataType = DATATYPE_DFT;
|
||||
clks_cal.DftSrc = AppIMPCfg.DftSrc;
|
||||
clks_cal.DataCount = 1L<<(AppIMPCfg.DftNum+2);
|
||||
|
|
@ -319,7 +395,7 @@ static AD5940Err AppIMPSeqMeasureGen(void)
|
|||
AD5940_SEQGenInsert(SEQ_WAIT(16*250));
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* Measurement 1: RCAL (Current) */
|
||||
/* Step 1: Measure Current across RCAL (Calibration) */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
sw_cfg.Dswitch = SWD_RCAL0;
|
||||
sw_cfg.Pswitch = SWP_RCAL0;
|
||||
|
|
@ -327,9 +403,10 @@ static AD5940Err AppIMPSeqMeasureGen(void)
|
|||
sw_cfg.Tswitch = SWT_RCAL1|SWT_TRTIA;
|
||||
AD5940_SWMatrixCfgS(&sw_cfg);
|
||||
|
||||
/* ADC Mux for Current (HSTIA) */
|
||||
// ADC Mux for Current (HSTIA)
|
||||
AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N);
|
||||
|
||||
// Enable AFE Power and Waveform Generator
|
||||
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
|
||||
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
|
||||
AFECTRL_SINC2NOTCH, bTRUE);
|
||||
|
|
@ -337,14 +414,18 @@ static AD5940Err AppIMPSeqMeasureGen(void)
|
|||
AD5940_SEQGenInsert(SEQ_WAIT(16*2000));
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bTRUE);
|
||||
|
||||
// Store address to update wait times later during sweep
|
||||
AD5940_SEQGenFetchSeq(NULL, &AppIMPCfg.SeqWaitAddr[0]);
|
||||
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
|
||||
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
|
||||
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG, bFALSE);
|
||||
// Stop ADC/DFT first, wait 10us, then stop WG
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bFALSE);
|
||||
AD5940_SEQGenInsert(SEQ_WAIT(16*10));
|
||||
AD5940_AFECtrlS(AFECTRL_WG, bFALSE);
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* Measurement 2: Sensor Current (I) */
|
||||
/* Step 2: Measure Sensor Current (I) */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
sw_cfg.Dswitch = AppIMPCfg.DswitchSel;
|
||||
sw_cfg.Pswitch = AppIMPCfg.PswitchSel;
|
||||
|
|
@ -352,7 +433,7 @@ static AD5940Err AppIMPSeqMeasureGen(void)
|
|||
sw_cfg.Tswitch = SWT_TRTIA|AppIMPCfg.TswitchSel;
|
||||
AD5940_SWMatrixCfgS(&sw_cfg);
|
||||
|
||||
/* ADC Mux for Current (HSTIA) */
|
||||
// ADC Mux for Current (HSTIA)
|
||||
AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N);
|
||||
|
||||
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_WG, bTRUE);
|
||||
|
|
@ -363,14 +444,16 @@ static AD5940Err AppIMPSeqMeasureGen(void)
|
|||
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
|
||||
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
|
||||
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG, bFALSE);
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bFALSE);
|
||||
AD5940_SEQGenInsert(SEQ_WAIT(16*10));
|
||||
AD5940_AFECtrlS(AFECTRL_WG, bFALSE);
|
||||
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* Measurement 3: Sensor Voltage (V) */
|
||||
/* Step 3: Measure Sensor Voltage (V) */
|
||||
/* ----------------------------------------------------------------------- */
|
||||
/* Switches remain same (Force path active) */
|
||||
// Switches remain same (Force path active)
|
||||
|
||||
/* ADC Mux for Voltage (AIN2/AIN3) */
|
||||
// ADC Mux for Voltage (AIN2/AIN3)
|
||||
AD5940_ADCMuxCfgS(ADCMUXP_AIN2, ADCMUXN_AIN3);
|
||||
|
||||
AD5940_AFECtrlS(AFECTRL_ADCPWR|AFECTRL_WG, bTRUE);
|
||||
|
|
@ -381,7 +464,12 @@ static AD5940Err AppIMPSeqMeasureGen(void)
|
|||
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
|
||||
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks/2));
|
||||
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG|AFECTRL_ADCPWR, bFALSE);
|
||||
// Stop ADC/DFT, wait 10us, then stop WG and ADC Power
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT, bFALSE);
|
||||
AD5940_SEQGenInsert(SEQ_WAIT(16*10));
|
||||
AD5940_AFECtrlS(AFECTRL_WG|AFECTRL_ADCPWR, bFALSE);
|
||||
|
||||
// Power down AFE blocks
|
||||
AD5940_AFECtrlS(AFECTRL_HSTIAPWR|AFECTRL_INAMPPWR|AFECTRL_EXTBUFPWR|\
|
||||
AFECTRL_WG|AFECTRL_DACREFPWR|AFECTRL_HSDACPWR|\
|
||||
AFECTRL_SINC2NOTCH, bFALSE);
|
||||
|
|
@ -433,7 +521,7 @@ AD5940Err AppIMPCheckFreq(float freq)
|
|||
AD5940_ADCFilterCfgS(&filter_cfg);
|
||||
AD5940_DFTCfgS(&dft_cfg);
|
||||
|
||||
// Calculate new WaitClks for the sequence
|
||||
// Recalculate settling times (WaitClks) for the new frequency
|
||||
clks_cal.DataType = DATATYPE_DFT;
|
||||
clks_cal.DftSrc = freq_params.DftSrc;
|
||||
clks_cal.DataCount = 1L<<(freq_params.DftNum+2);
|
||||
|
|
@ -471,7 +559,7 @@ int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize)
|
|||
|
||||
if(AD5940_WakeUp(10) > 10) return AD5940ERR_WAKEUP;
|
||||
|
||||
// CRITICAL: Stop WUPT and Sequencer before reconfiguration
|
||||
// Stop timers and sequencer before reconfiguration
|
||||
AD5940_WUPTCtrl(bFALSE);
|
||||
AD5940_SEQCtrlS(bFALSE);
|
||||
|
||||
|
|
@ -515,7 +603,7 @@ int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize)
|
|||
|
||||
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ENDSEQ, bTRUE);
|
||||
|
||||
// Safety timeout for Init Sequence
|
||||
// Wait for initialization sequence to complete
|
||||
int timeout = 100000;
|
||||
while(AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE) {
|
||||
if(--timeout <= 0) {
|
||||
|
|
@ -547,7 +635,7 @@ int32_t AppIMPRegModify(int32_t * const pData, uint32_t *pDataCount)
|
|||
{
|
||||
AD5940_WUPTCtrl(bFALSE);
|
||||
AD5940_SEQCtrlS(bFALSE);
|
||||
return AD5940ERR_OK;
|
||||
return AD5940ERR_STOP; // Return STOP code
|
||||
}
|
||||
}
|
||||
if(AppIMPCfg.StopRequired == bTRUE)
|
||||
|
|
@ -603,6 +691,7 @@ int32_t AppIMPDataProcess(int32_t * const pData, uint32_t *pDataCount)
|
|||
MagV = sqrt((float)pDftV->Real*pDftV->Real + (float)pDftV->Image*pDftV->Image);
|
||||
PhaseV = atan2(-pDftV->Image, pDftV->Real);
|
||||
|
||||
// Calculate Impedance: Z = (V / I) * Rtia
|
||||
if(MagI > 1e-9)
|
||||
ZMag = (MagV / MagI) * AppIMPCfg.RtiaVal;
|
||||
else
|
||||
|
|
@ -676,7 +765,8 @@ int32_t AppIMPISR(void *pBuff, uint32_t *pCount)
|
|||
{
|
||||
AD5940_SEQCtrlS(bFALSE);
|
||||
|
||||
if (AppIMPRegModify(pBuff, &FifoCnt) == AD5940ERR_OK)
|
||||
int32_t status = AppIMPRegModify(pBuff, &FifoCnt);
|
||||
if (status == AD5940ERR_OK)
|
||||
{
|
||||
if(AppIMPCfg.FifoDataCount < AppIMPCfg.NumOfData || AppIMPCfg.NumOfData == -1)
|
||||
{
|
||||
|
|
@ -693,7 +783,19 @@ int32_t AppIMPISR(void *pBuff, uint32_t *pCount)
|
|||
|
||||
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
|
||||
AppIMPDataProcess((int32_t*)pBuff, &FifoCnt);
|
||||
*pCount = FifoCnt;
|
||||
|
||||
// Discard extra data points if they exceed the requested count
|
||||
if (AppIMPCfg.SweepCfg.SweepEn && AppIMPCfg.RealDataCount > 0) {
|
||||
if (AppIMPCfg.FifoDataCount > AppIMPCfg.RealDataCount) {
|
||||
*pCount = 0; // Discard
|
||||
} else {
|
||||
*pCount = FifoCnt;
|
||||
}
|
||||
} else {
|
||||
*pCount = FifoCnt;
|
||||
}
|
||||
|
||||
if (status == AD5940ERR_STOP) return AD5940ERR_STOP;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ typedef struct
|
|||
/* 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 */
|
||||
|
|
@ -59,6 +60,7 @@ typedef struct
|
|||
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;
|
||||
|
||||
#define IMPCTRL_START 0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,360 @@
|
|||
// File: Measurement_Core.c
|
||||
#include "App_Common.h"
|
||||
|
||||
void AD5941_InitAll(void) {
|
||||
AD5940_HWReset();
|
||||
AD5940_MCUResourceInit(0);
|
||||
AD5940_Initialize();
|
||||
// CRITICAL: Re-enable Platform Interrupts/GPIOs after HW Reset
|
||||
AD5940PlatformCfg();
|
||||
}
|
||||
|
||||
void AD5940ImpedanceStructInit(void) {
|
||||
AppIMPCfg_Type *pImpedanceCfg;
|
||||
AppIMPGetCfg(&pImpedanceCfg);
|
||||
|
||||
pImpedanceCfg->IMPInited = bFALSE;
|
||||
pImpedanceCfg->SeqStartAddr = 0;
|
||||
pImpedanceCfg->MaxSeqLen = 512;
|
||||
pImpedanceCfg->RcalVal = 100.0;
|
||||
pImpedanceCfg->RtiaVal = CalibratedHstiaVal;
|
||||
pImpedanceCfg->SinFreq = 1000.0;
|
||||
pImpedanceCfg->FifoThresh = 6;
|
||||
pImpedanceCfg->DacVoltPP = 600.0;
|
||||
pImpedanceCfg->ExcitBufGain = EXCITBUFGAIN_0P25;
|
||||
pImpedanceCfg->HsDacGain = HSDACGAIN_0P2;
|
||||
pImpedanceCfg->HsDacUpdateRate = 7;
|
||||
|
||||
// --- Switch Matrix Configuration ---
|
||||
// D-Switch: Connect DAC to CE0 (Force +)
|
||||
pImpedanceCfg->DswitchSel = SWD_CE0;
|
||||
|
||||
// P-Switch: Connect CE0 to P-Bus (Optional, but standard)
|
||||
pImpedanceCfg->PswitchSel = SWP_CE0;
|
||||
|
||||
// N-Switch: Connect SE0 to N-Bus (Force - / Current Input)
|
||||
// This is the CRITICAL path for HSTIA current measurement.
|
||||
pImpedanceCfg->NswitchSel = SWN_SE0;
|
||||
|
||||
// T-Switch: Only connect the Feedback Resistor (TRTIA).
|
||||
// DO NOT connect SWT_SE0 (T11), as it may short SE0 to RE0/LPTIA nodes.
|
||||
pImpedanceCfg->TswitchSel = 0; // Impedance.c adds SWT_TRTIA automatically
|
||||
|
||||
pImpedanceCfg->HstiaRtiaSel = GetHSTIARtia(ConfigHstiaVal);
|
||||
pImpedanceCfg->BiasVolt = 0.0;
|
||||
pImpedanceCfg->SweepCfg.SweepEn = bFALSE;
|
||||
pImpedanceCfg->SweepCfg.SweepStart = 100.0f;
|
||||
pImpedanceCfg->SweepCfg.SweepStop = 100000.0f;
|
||||
pImpedanceCfg->SweepCfg.SweepPoints = 50;
|
||||
pImpedanceCfg->SweepCfg.SweepLog = bTRUE;
|
||||
pImpedanceCfg->PwrMod = AFEPWR_LP;
|
||||
pImpedanceCfg->ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
pImpedanceCfg->DftNum = DFTNUM_16384;
|
||||
pImpedanceCfg->DftSrc = DFTSRC_SINC3;
|
||||
pImpedanceCfg->ShortRe0Se0 = GlobalShortRe0Se0;
|
||||
}
|
||||
|
||||
void AD5940AMPStructInit(void) {
|
||||
AppAMPCfg_Type *pAMPCfg;
|
||||
AppAMPGetCfg(&pAMPCfg);
|
||||
|
||||
pAMPCfg->AMPInited = bFALSE;
|
||||
pAMPCfg->WuptClkFreq = LFOSCFreq;
|
||||
pAMPCfg->SeqStartAddr = 0;
|
||||
pAMPCfg->MaxSeqLen = 512;
|
||||
pAMPCfg->RcalVal = 100.0;
|
||||
pAMPCfg->NumOfData = -1;
|
||||
pAMPCfg->AmpODR = 1.0;
|
||||
pAMPCfg->FifoThresh = 4;
|
||||
pAMPCfg->SensorBias = 0;
|
||||
pAMPCfg->LptiaRtiaSel = GetLPTIARtia(ConfigLptiaVal);
|
||||
pAMPCfg->LpTiaRl = ConfigRLoad;
|
||||
pAMPCfg->LpTiaRf = CurrentLpTiaRf;
|
||||
pAMPCfg->Vzero = 1100;
|
||||
pAMPCfg->ADCRefVolt = 1.82;
|
||||
pAMPCfg->RtiaCalValue.Magnitude = CalibratedLptiaVal;
|
||||
pAMPCfg->ShortRe0Se0 = GlobalShortRe0Se0;
|
||||
}
|
||||
|
||||
void AD5940RampStructInit(void) {
|
||||
AppRAMPCfg_Type *pRampCfg;
|
||||
AppRAMPGetCfg(&pRampCfg);
|
||||
|
||||
pRampCfg->RAMPInited = bFALSE;
|
||||
pRampCfg->SeqStartAddr = 0;
|
||||
pRampCfg->MaxSeqLen = 1024;
|
||||
pRampCfg->RcalVal = 100.0;
|
||||
pRampCfg->ADCRefVolt = 1820.0f;
|
||||
pRampCfg->FifoThresh = 4;
|
||||
pRampCfg->SysClkFreq = 16000000.0f;
|
||||
pRampCfg->LFOSCClkFreq = LFOSCFreq;
|
||||
|
||||
pRampCfg->RampStartVolt = -500.0f;
|
||||
pRampCfg->RampPeakVolt = +500.0f;
|
||||
pRampCfg->VzeroStart = 1100.0f;
|
||||
pRampCfg->VzeroPeak = 1100.0f;
|
||||
pRampCfg->StepNumber = 100;
|
||||
pRampCfg->RampDuration = 10000;
|
||||
pRampCfg->SampleDelay = 1.0f;
|
||||
|
||||
pRampCfg->LPTIARtiaSel = GetLPTIARtia(ConfigLptiaVal);
|
||||
pRampCfg->LPTIARloadSel = ConfigRLoad;
|
||||
pRampCfg->LpTiaRf = CurrentLpTiaRf;
|
||||
pRampCfg->AdcPgaGain = ADCPGA_1P5;
|
||||
pRampCfg->RtiaValue.Magnitude = CalibratedLptiaVal;
|
||||
pRampCfg->ShortRe0Se0 = GlobalShortRe0Se0;
|
||||
}
|
||||
|
||||
void Config_LPLOOP(float bias_mv) {
|
||||
uint32_t vzero_code = 32;
|
||||
float vzero_volts = vzero_code * (2200.0f / 64.0f);
|
||||
float vbias_volts = vzero_volts + bias_mv;
|
||||
uint32_t vbias_code = (uint32_t)(vbias_volts / (2200.0f / 4095.0f));
|
||||
if(vbias_code > 4095) vbias_code = 4095;
|
||||
|
||||
LPDACCfg_Type lpdac_cfg;
|
||||
lpdac_cfg.LpdacSel = LPDAC0;
|
||||
lpdac_cfg.LpDacVbiasMux = LPDACVBIAS_12BIT;
|
||||
lpdac_cfg.LpDacVzeroMux = LPDACVZERO_6BIT;
|
||||
lpdac_cfg.DacData6Bit = vzero_code;
|
||||
lpdac_cfg.DacData12Bit = vbias_code;
|
||||
lpdac_cfg.DataRst = bFALSE;
|
||||
lpdac_cfg.LpDacSW = LPDACSW_VBIAS2LPPA | LPDACSW_VBIAS2PIN | LPDACSW_VZERO2LPTIA | LPDACSW_VZERO2PIN;
|
||||
lpdac_cfg.LpDacRef = LPDACREF_2P5;
|
||||
lpdac_cfg.LpDacSrc = LPDACSRC_MMR;
|
||||
lpdac_cfg.PowerEn = bTRUE;
|
||||
AD5940_LPDACCfgS(&lpdac_cfg);
|
||||
|
||||
LPLoopCfg_Type lp_loop;
|
||||
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 = CurrentLpTiaRf;
|
||||
lp_loop.LpAmpCfg.LpTiaRload = ConfigRLoad;
|
||||
lp_loop.LpAmpCfg.LpTiaRtia = GetLPTIARtia(ConfigLptiaVal);
|
||||
|
||||
// Base Switches
|
||||
lp_loop.LpAmpCfg.LpTiaSW = LPTIASW(5)|LPTIASW(2)|LPTIASW(4)|LPTIASW(12)|LPTIASW(13);
|
||||
|
||||
// Apply Short Option
|
||||
if(GlobalShortRe0Se0) {
|
||||
lp_loop.LpAmpCfg.LpTiaSW |= LPTIASW(11);
|
||||
}
|
||||
|
||||
AD5940_LPLoopCfgS(&lp_loop);
|
||||
}
|
||||
|
||||
void Calibrate_HSDAC(float freq) {
|
||||
HSDACCal_Type hsdac_cal;
|
||||
ADCPGACal_Type adcpga_cal;
|
||||
CLKCfg_Type clk_cfg;
|
||||
|
||||
// Configure Clock based on frequency (matches Do_WaveGen logic)
|
||||
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
|
||||
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
|
||||
clk_cfg.SysClkDiv = SYSCLKDIV_1;
|
||||
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
|
||||
clk_cfg.HFXTALEn = bFALSE;
|
||||
clk_cfg.LFOSCEn = bTRUE;
|
||||
|
||||
if(freq > 80000) {
|
||||
clk_cfg.HfOSC32MHzMode = bTRUE;
|
||||
clk_cfg.HFOSCEn = bTRUE;
|
||||
AD5940_CLKCfg(&clk_cfg);
|
||||
AD5940_HPModeEn(bTRUE);
|
||||
} else {
|
||||
clk_cfg.HfOSC32MHzMode = bFALSE;
|
||||
clk_cfg.HFOSCEn = bTRUE;
|
||||
AD5940_CLKCfg(&clk_cfg);
|
||||
AD5940_HPModeEn(bFALSE);
|
||||
}
|
||||
|
||||
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
|
||||
|
||||
adcpga_cal.AdcClkFreq = 16000000;
|
||||
adcpga_cal.ADCPga = ADCPGA_1P5;
|
||||
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);
|
||||
|
||||
hsdac_cal.ExcitBufGain = EXCITBUFGAIN_2;
|
||||
hsdac_cal.HsDacGain = HSDACGAIN_1;
|
||||
hsdac_cal.AfePwrMode = AFEPWR_LP;
|
||||
hsdac_cal.ADCSinc2Osr = ADCSINC2OSR_1333;
|
||||
hsdac_cal.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
AD5940_HSDACCal(&hsdac_cal);
|
||||
|
||||
hsdac_cal.ExcitBufGain = EXCITBUFGAIN_2;
|
||||
hsdac_cal.HsDacGain = HSDACGAIN_0P2;
|
||||
AD5940_HSDACCal(&hsdac_cal);
|
||||
|
||||
hsdac_cal.ExcitBufGain = EXCITBUFGAIN_0P25;
|
||||
hsdac_cal.HsDacGain = HSDACGAIN_1;
|
||||
AD5940_HSDACCal(&hsdac_cal);
|
||||
|
||||
hsdac_cal.ExcitBufGain = EXCITBUFGAIN_0P25;
|
||||
hsdac_cal.HsDacGain = HSDACGAIN_0P2;
|
||||
AD5940_HSDACCal(&hsdac_cal);
|
||||
}
|
||||
|
||||
void Configure_Filters(float freq) {
|
||||
ADCFilterCfg_Type adc_filter;
|
||||
DFTCfg_Type dft_cfg;
|
||||
FIFOCfg_Type fifo_cfg;
|
||||
|
||||
fifo_cfg.FIFOEn = bFALSE;
|
||||
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
|
||||
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
|
||||
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
|
||||
fifo_cfg.FIFOThresh = 2;
|
||||
AD5940_FIFOCfg(&fifo_cfg);
|
||||
fifo_cfg.FIFOEn = bTRUE;
|
||||
AD5940_FIFOCfg(&fifo_cfg);
|
||||
|
||||
AD5940_ADCMuxCfgS(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N);
|
||||
AD5940_StructInit(&adc_filter, sizeof(adc_filter));
|
||||
AD5940_StructInit(&dft_cfg, sizeof(dft_cfg));
|
||||
|
||||
if (freq < 0.51f) {
|
||||
adc_filter.ADCAvgNum = ADCAVGNUM_16;
|
||||
adc_filter.ADCSinc2Osr = ADCSINC2OSR_267;
|
||||
adc_filter.ADCSinc3Osr = ADCSINC3OSR_5;
|
||||
adc_filter.BpNotch = bTRUE;
|
||||
adc_filter.BpSinc3 = bFALSE;
|
||||
adc_filter.Sinc2NotchEnable = bTRUE;
|
||||
adc_filter.ADCRate = ADCRATE_800KHZ;
|
||||
dft_cfg.DftNum = DFTNUM_8192;
|
||||
dft_cfg.DftSrc = DFTSRC_SINC2NOTCH;
|
||||
} else if(freq < 5.0f) {
|
||||
adc_filter.ADCAvgNum = ADCAVGNUM_16;
|
||||
adc_filter.ADCSinc2Osr = ADCSINC2OSR_178;
|
||||
adc_filter.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
adc_filter.BpNotch = bTRUE;
|
||||
adc_filter.BpSinc3 = bFALSE;
|
||||
adc_filter.Sinc2NotchEnable = bTRUE;
|
||||
adc_filter.ADCRate = ADCRATE_800KHZ;
|
||||
dft_cfg.DftNum = DFTNUM_8192;
|
||||
dft_cfg.DftSrc = DFTSRC_SINC2NOTCH;
|
||||
} else if(freq < 450.0f) {
|
||||
adc_filter.ADCAvgNum = ADCAVGNUM_16;
|
||||
adc_filter.ADCSinc2Osr = ADCSINC2OSR_44;
|
||||
adc_filter.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
adc_filter.BpNotch = bTRUE;
|
||||
adc_filter.BpSinc3 = bFALSE;
|
||||
adc_filter.Sinc2NotchEnable = bTRUE;
|
||||
adc_filter.ADCRate = ADCRATE_800KHZ;
|
||||
dft_cfg.DftNum = DFTNUM_4096;
|
||||
dft_cfg.DftSrc = DFTSRC_SINC2NOTCH;
|
||||
} else if(freq < 80000.0f) {
|
||||
adc_filter.ADCAvgNum = ADCAVGNUM_16;
|
||||
adc_filter.ADCSinc2Osr = ADCSINC2OSR_178;
|
||||
adc_filter.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
adc_filter.BpNotch = bTRUE;
|
||||
adc_filter.BpSinc3 = bFALSE;
|
||||
adc_filter.Sinc2NotchEnable = bFALSE;
|
||||
adc_filter.ADCRate = ADCRATE_800KHZ;
|
||||
dft_cfg.DftNum = DFTNUM_16384;
|
||||
dft_cfg.DftSrc = DFTSRC_SINC3;
|
||||
} else {
|
||||
adc_filter.ADCAvgNum = ADCAVGNUM_16;
|
||||
adc_filter.ADCSinc2Osr = ADCSINC2OSR_178;
|
||||
adc_filter.ADCSinc3Osr = ADCSINC3OSR_2;
|
||||
adc_filter.BpNotch = bTRUE;
|
||||
adc_filter.BpSinc3 = bFALSE;
|
||||
adc_filter.Sinc2NotchEnable = bFALSE;
|
||||
adc_filter.ADCRate = ADCRATE_1P6MHZ;
|
||||
dft_cfg.DftNum = DFTNUM_16384;
|
||||
dft_cfg.DftSrc = DFTSRC_SINC3;
|
||||
}
|
||||
dft_cfg.HanWinEn = bTRUE;
|
||||
|
||||
AD5940_ADCFilterCfgS(&adc_filter);
|
||||
AD5940_DFTCfgS(&dft_cfg);
|
||||
}
|
||||
|
||||
void Do_WaveGen(float freq) {
|
||||
CLKCfg_Type clk_cfg;
|
||||
AFERefCfg_Type aferef_cfg;
|
||||
HSLoopCfg_Type HpLoopCfg;
|
||||
ADCBaseCfg_Type adc_base;
|
||||
|
||||
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
|
||||
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
|
||||
clk_cfg.SysClkDiv = SYSCLKDIV_1;
|
||||
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
|
||||
clk_cfg.HFXTALEn = bFALSE;
|
||||
clk_cfg.LFOSCEn = bTRUE;
|
||||
|
||||
if(freq > 80000) {
|
||||
clk_cfg.HfOSC32MHzMode = bTRUE;
|
||||
clk_cfg.HFOSCEn = bTRUE;
|
||||
AD5940_CLKCfg(&clk_cfg);
|
||||
AD5940_HPModeEn(bTRUE);
|
||||
} else {
|
||||
clk_cfg.HfOSC32MHzMode = bFALSE;
|
||||
clk_cfg.HFOSCEn = bTRUE;
|
||||
AD5940_CLKCfg(&clk_cfg);
|
||||
AD5940_HPModeEn(bFALSE);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
HpLoopCfg.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2;
|
||||
HpLoopCfg.HsDacCfg.HsDacGain = HSDACGAIN_1;
|
||||
HpLoopCfg.HsDacCfg.HsDacUpdateRate = (freq > 80000) ? 0x07 : 0x1B;
|
||||
|
||||
HpLoopCfg.HsTiaCfg.DiodeClose = bFALSE;
|
||||
HpLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
|
||||
HpLoopCfg.HsTiaCfg.HstiaCtia = 16;
|
||||
HpLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
|
||||
HpLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_TODE;
|
||||
HpLoopCfg.HsTiaCfg.HstiaRtiaSel = GetHSTIARtia(ConfigHstiaVal);
|
||||
|
||||
adc_base.ADCPga = ADCPGA_1P5;
|
||||
AD5940_ADCBaseCfgS(&adc_base);
|
||||
|
||||
// --- Switch Matrix Configuration (Manual Sweep) ---
|
||||
HpLoopCfg.SWMatCfg.Dswitch = SWD_CE0;
|
||||
HpLoopCfg.SWMatCfg.Pswitch = SWP_CE0;
|
||||
HpLoopCfg.SWMatCfg.Nswitch = SWN_SE0;
|
||||
// FIX: Only use TRTIA (Feedback). Do NOT use SWT_SE0.
|
||||
HpLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA;
|
||||
|
||||
AD5940_AFECtrlS(AFECTRL_WG, bFALSE);
|
||||
|
||||
HpLoopCfg.WgCfg.WgType = WGTYPE_SIN;
|
||||
HpLoopCfg.WgCfg.GainCalEn = bFALSE;
|
||||
HpLoopCfg.WgCfg.OffsetCalEn = bFALSE;
|
||||
HpLoopCfg.WgCfg.SinCfg.SinFreqWord = AD5940_WGFreqWordCal(freq, (freq > 80000) ? 32000000.0 : 16000000.0);
|
||||
|
||||
// Reduced Amplitude: 50mV peak (100mV pp) to prevent saturation
|
||||
// Range is +/- 607mV with Gain 2/1.
|
||||
// 50 / 607 * 2047 = ~168
|
||||
HpLoopCfg.WgCfg.SinCfg.SinAmplitudeWord = (uint32_t)(50.0f/607.0f*2047 + 0.5f);
|
||||
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_AFECtrlS(AFECTRL_DCBUFPWR, bTRUE);
|
||||
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
// File: Measurement_Routines.c
|
||||
#include "App_Common.h"
|
||||
|
||||
void Routine_CalibrateLFO(void) {
|
||||
printf(">> Calibrating LFOSC...\n");
|
||||
if (CurrentMode == MODE_IMPEDANCE) AppIMPCleanup();
|
||||
else if (CurrentMode == MODE_AMPEROMETRIC) AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
|
||||
else if (CurrentMode == MODE_RAMP) AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
|
||||
|
||||
LFOSCMeasure_Type cal_cfg;
|
||||
cal_cfg.CalDuration = 1000.0;
|
||||
cal_cfg.CalSeqAddr = 0;
|
||||
cal_cfg.SystemClkFreq = 16000000.0;
|
||||
|
||||
if(AD5940_LFOSCMeasure(&cal_cfg, &LFOSCFreq) == AD5940ERR_OK) {
|
||||
printf(">> LFOSC Calibrated: %.2f Hz\n", LFOSCFreq);
|
||||
} else {
|
||||
printf(">> LFOSC Calibration Failed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Routine_Measure(float freq) {
|
||||
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);
|
||||
AppIMPCleanup();
|
||||
|
||||
AD5940ImpedanceStructInit(); // Reload config with current HP settings
|
||||
|
||||
pCfg->WuptClkFreq = LFOSCFreq;
|
||||
pCfg->SweepCfg.SweepEn = bFALSE;
|
||||
pCfg->SinFreq = freq;
|
||||
pCfg->NumOfData = -1;
|
||||
pCfg->RealDataCount = -1;
|
||||
pCfg->bParaChanged = bTRUE;
|
||||
|
||||
if(AppIMPInit(AppBuff, APPBUFF_SIZE) == AD5940ERR_OK) {
|
||||
AppIMPCtrl(IMPCTRL_START, 0);
|
||||
} else {
|
||||
printf("ERROR: Init Failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Routine_Sweep(float start, float end, int steps) {
|
||||
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);
|
||||
AppIMPCleanup();
|
||||
|
||||
AD5940ImpedanceStructInit(); // Reload config with current HP settings
|
||||
|
||||
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;
|
||||
pCfg->bParaChanged = bTRUE;
|
||||
|
||||
if(AppIMPInit(AppBuff, APPBUFF_SIZE) == AD5940ERR_OK) {
|
||||
AppIMPCtrl(IMPCTRL_START, 0);
|
||||
} else {
|
||||
printf("ERROR: Init Failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Routine_Amperometric(float bias_mv) {
|
||||
if (CurrentMode == MODE_IMPEDANCE) AppIMPCleanup();
|
||||
else if (CurrentMode == MODE_RAMP) AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
|
||||
CurrentMode = MODE_AMPEROMETRIC;
|
||||
|
||||
printf(">> Starting Amperometry (Bias: %.1f mV, LP Range: %d)...\n", bias_mv, ConfigLptiaVal);
|
||||
|
||||
AppAMPCfg_Type *pCfg;
|
||||
AppAMPGetCfg(&pCfg);
|
||||
AD5940AMPStructInit(); // Reload config with current LP settings
|
||||
pCfg->SensorBias = bias_mv;
|
||||
pCfg->ReDoRtiaCal = bFALSE; // Use pre-calibrated value
|
||||
|
||||
if(AppAMPInit(AppBuff, APPBUFF_SIZE) == AD5940ERR_OK) {
|
||||
AppAMPCtrl(AMPCTRL_START, 0);
|
||||
} else {
|
||||
printf("ERROR: AMP Init Failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Routine_LSV(float start_mv, float end_mv, int steps, int duration_ms) {
|
||||
if (CurrentMode == MODE_IMPEDANCE) AppIMPCleanup();
|
||||
else if (CurrentMode == MODE_AMPEROMETRIC) AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
|
||||
CurrentMode = MODE_RAMP;
|
||||
|
||||
printf(">> Starting LSV (%.1f to %.1f mV, %d steps, %d ms, LP Range: %d)...\n", start_mv, end_mv, steps, duration_ms, ConfigLptiaVal);
|
||||
|
||||
AppRAMPCfg_Type *pCfg;
|
||||
AppRAMPGetCfg(&pCfg);
|
||||
AD5940RampStructInit(); // Reload config with current LP settings
|
||||
|
||||
pCfg->RampStartVolt = start_mv;
|
||||
pCfg->RampPeakVolt = end_mv;
|
||||
pCfg->StepNumber = steps;
|
||||
pCfg->RampDuration = duration_ms;
|
||||
pCfg->bRampOneDir = bTRUE;
|
||||
pCfg->bParaChanged = bTRUE;
|
||||
|
||||
if(AppRAMPInit(AppBuff, APPBUFF_SIZE) == AD5940ERR_OK) {
|
||||
AppRAMPCtrl(APPCTRL_START, 0);
|
||||
} else {
|
||||
printf("ERROR: RAMP Init Failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Routine_CalibrateSystem(void) {
|
||||
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);
|
||||
AppIMPCleanup();
|
||||
|
||||
ADCPGACal_Type adcpga_cal;
|
||||
adcpga_cal.AdcClkFreq = 16000000.0;
|
||||
adcpga_cal.SysClkFreq = 16000000.0;
|
||||
adcpga_cal.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
adcpga_cal.ADCSinc2Osr = ADCSINC2OSR_22;
|
||||
adcpga_cal.ADCPga = ADCPGA_1P5;
|
||||
adcpga_cal.PGACalType = PGACALTYPE_OFFSET;
|
||||
adcpga_cal.TimeOut10us = 1000;
|
||||
adcpga_cal.VRef1p11 = 1.11;
|
||||
adcpga_cal.VRef1p82 = 1.82;
|
||||
printf(">> Calibrating ADC Offset...\n");
|
||||
AD5940_ADCPGACal(&adcpga_cal);
|
||||
|
||||
// --- 1. Calibrate LPTIA (Low Power Loop) ---
|
||||
LPRTIACal_Type lprtia_cal;
|
||||
fImpPol_Type LpRes;
|
||||
memset(&lprtia_cal, 0, sizeof(lprtia_cal));
|
||||
lprtia_cal.AdcClkFreq = 16000000.0;
|
||||
lprtia_cal.SysClkFreq = 16000000.0;
|
||||
lprtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
lprtia_cal.ADCSinc2Osr = ADCSINC2OSR_22;
|
||||
lprtia_cal.bPolarResult = bTRUE;
|
||||
lprtia_cal.fRcal = 100.0;
|
||||
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("Calibrated LPTIA: Mag = %f Ohm, Phase = %f\n", LpRes.Magnitude, LpRes.Phase);
|
||||
CalibratedLptiaVal = LpRes.Magnitude;
|
||||
} else {
|
||||
printf("LPTIA Calibration Failed\n");
|
||||
}
|
||||
|
||||
// --- 2. Calibrate HSTIA (High Speed Loop) ---
|
||||
HSDACCfg_Type hsdac_cfg;
|
||||
hsdac_cfg.ExcitBufGain = EXCITBUFGAIN_0P25;
|
||||
hsdac_cfg.HsDacGain = HSDACGAIN_0P2;
|
||||
hsdac_cfg.HsDacUpdateRate = 7;
|
||||
AD5940_HSDacCfgS(&hsdac_cfg);
|
||||
|
||||
HSRTIACal_Type hsrtia_cal;
|
||||
fImpPol_Type HsRes;
|
||||
memset(&hsrtia_cal, 0, sizeof(hsrtia_cal));
|
||||
hsrtia_cal.fFreq = 1000.0f;
|
||||
hsrtia_cal.AdcClkFreq = 16000000.0;
|
||||
hsrtia_cal.SysClkFreq = 16000000.0;
|
||||
hsrtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
hsrtia_cal.ADCSinc2Osr = ADCSINC2OSR_22;
|
||||
hsrtia_cal.bPolarResult = bTRUE;
|
||||
hsrtia_cal.fRcal = 100.0;
|
||||
hsrtia_cal.HsTiaCfg.DiodeClose = bFALSE;
|
||||
hsrtia_cal.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
|
||||
hsrtia_cal.HsTiaCfg.HstiaCtia = 31;
|
||||
hsrtia_cal.HsTiaCfg.HstiaRtiaSel = GetHSTIARtia(ConfigHstiaVal);
|
||||
hsrtia_cal.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
|
||||
hsrtia_cal.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
|
||||
hsrtia_cal.DftCfg.DftNum = DFTNUM_16384;
|
||||
hsrtia_cal.DftCfg.DftSrc = DFTSRC_SINC3;
|
||||
|
||||
printf(">> Calibrating HSTIA %d Ohm...\n", ConfigHstiaVal);
|
||||
if (AD5940_HSRtiaCal(&hsrtia_cal, &HsRes) == AD5940ERR_OK) {
|
||||
printf("Calibrated HSTIA: Mag = %f Ohm, Phase = %f\n", HsRes.Magnitude, HsRes.Phase);
|
||||
CalibratedHstiaVal = HsRes.Magnitude;
|
||||
} else {
|
||||
printf("HSTIA Calibration Failed\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,596 @@
|
|||
// File: RampTest.c
|
||||
#include "ad5940.h"
|
||||
#include <stdio.h>
|
||||
#include "string.h"
|
||||
#include "math.h"
|
||||
#include "RampTest.h"
|
||||
|
||||
#define AD5940ERR_STOP 10
|
||||
|
||||
AppRAMPCfg_Type AppRAMPCfg =
|
||||
{
|
||||
.bParaChanged = bFALSE,
|
||||
.SeqStartAddr = 0,
|
||||
.MaxSeqLen = 0,
|
||||
.SeqStartAddrCal = 0,
|
||||
.MaxSeqLenCal = 0,
|
||||
|
||||
.LFOSCClkFreq = 32000.0,
|
||||
.SysClkFreq = 16000000.0,
|
||||
.AdcClkFreq = 16000000.0,
|
||||
.RcalVal = 100.0,
|
||||
.ADCRefVolt = 1820.0f,
|
||||
.bTestFinished = bFALSE,
|
||||
|
||||
.RampStartVolt = -500.0f,
|
||||
.RampPeakVolt = +500.0f,
|
||||
.VzeroStart = 1100.0f,
|
||||
.VzeroPeak = 1100.0f,
|
||||
.StepNumber = 100,
|
||||
.RampDuration = 10000,
|
||||
|
||||
.SampleDelay = 1.0f,
|
||||
.LPTIARtiaSel = LPTIARTIA_4K,
|
||||
.LpTiaRf = LPTIARF_20K, /* Default LPF */
|
||||
.ExternalRtiaValue = 20000.0f,
|
||||
.AdcPgaGain = ADCPGA_1P5,
|
||||
.ADCSinc3Osr = ADCSINC3OSR_2,
|
||||
.FifoThresh = 4,
|
||||
|
||||
.RAMPInited = bFALSE,
|
||||
.StopRequired = bFALSE,
|
||||
.RampState = RAMP_STATE0,
|
||||
.bFirstDACSeq = bTRUE,
|
||||
.bRampOneDir = bFALSE,
|
||||
.ShortRe0Se0 = bFALSE,
|
||||
};
|
||||
|
||||
AD5940Err AppRAMPGetCfg(void *pCfg)
|
||||
{
|
||||
if(pCfg)
|
||||
{
|
||||
*(AppRAMPCfg_Type **)pCfg = &AppRAMPCfg;
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
return AD5940ERR_PARA;
|
||||
}
|
||||
|
||||
AD5940Err AppRAMPCtrl(uint32_t Command, void *pPara)
|
||||
{
|
||||
switch (Command)
|
||||
{
|
||||
case APPCTRL_START:
|
||||
{
|
||||
WUPTCfg_Type wupt_cfg;
|
||||
|
||||
if(AD5940_WakeUp(10) > 10) return AD5940ERR_WAKEUP;
|
||||
if(AppRAMPCfg.RAMPInited == bFALSE) return AD5940ERR_APPERROR;
|
||||
|
||||
// --- CRITICAL FIX: Reset State on Start ---
|
||||
AppRAMPCfg.RampState = RAMP_STATE0;
|
||||
AppRAMPCfg.CurrStepPos = 0;
|
||||
AppRAMPCfg.bFirstDACSeq = bTRUE;
|
||||
AppRAMPCfg.StopRequired = bFALSE;
|
||||
AppRAMPCfg.FifoThresh = 4;
|
||||
// ------------------------------------------
|
||||
|
||||
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) return AD5940ERR_WAKEUP;
|
||||
AD5940_WUPTCtrl(bFALSE);
|
||||
AD5940_WUPTCtrl(bFALSE);
|
||||
break;
|
||||
}
|
||||
case APPCTRL_STOPSYNC:
|
||||
{
|
||||
AppRAMPCfg.StopRequired = bTRUE;
|
||||
break;
|
||||
}
|
||||
case APPCTRL_SHUTDOWN:
|
||||
{
|
||||
AppRAMPCtrl(APPCTRL_STOPNOW, 0);
|
||||
AD5940_ShutDownS();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
AD5940_SEQGenCtrl(bTRUE);
|
||||
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
|
||||
|
||||
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;
|
||||
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_NORM;
|
||||
lploop_cfg.LpAmpCfg.LpPaPwrEn = bTRUE;
|
||||
lploop_cfg.LpAmpCfg.LpTiaPwrEn = bTRUE;
|
||||
lploop_cfg.LpAmpCfg.LpTiaRf = AppRAMPCfg.LpTiaRf; // Use Configured LPF
|
||||
lploop_cfg.LpAmpCfg.LpTiaRload = LPTIARLOAD_10R;
|
||||
lploop_cfg.LpAmpCfg.LpTiaRtia = AppRAMPCfg.LPTIARtiaSel;
|
||||
|
||||
lploop_cfg.LpAmpCfg.LpTiaSW = LPTIASW(5)|LPTIASW(2)|LPTIASW(4)|LPTIASW(12)|LPTIASW(13);
|
||||
|
||||
// Apply Short Option
|
||||
if(AppRAMPCfg.ShortRe0Se0) {
|
||||
lploop_cfg.LpAmpCfg.LpTiaSW |= LPTIASW(11);
|
||||
}
|
||||
|
||||
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;
|
||||
lploop_cfg.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT;
|
||||
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;
|
||||
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
|
||||
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
|
||||
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
|
||||
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = ADCSINC2OSR_1067;
|
||||
dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_2;
|
||||
AD5940_DSPCfgS(&dsp_cfg);
|
||||
|
||||
AD5940_SEQGenInsert(SEQ_STOP());
|
||||
|
||||
AD5940_SEQGenCtrl(bFALSE);
|
||||
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;
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
|
||||
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;
|
||||
clks_cal.DataType = DATATYPE_SINC3;
|
||||
clks_cal.ADCSinc3Osr = AppRAMPCfg.ADCSinc3Osr;
|
||||
clks_cal.ADCSinc2Osr = ADCSINC2OSR_1067;
|
||||
clks_cal.ADCAvgNum = ADCAVGNUM_2;
|
||||
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));
|
||||
AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE);
|
||||
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks));
|
||||
AD5940_AFECtrlS(AFECTRL_ADCPWR | AFECTRL_ADCCNV, bFALSE);
|
||||
AD5940_SEQGpioCtrlS(0);
|
||||
AD5940_EnterSleepS();
|
||||
|
||||
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
|
||||
AD5940_SEQGenCtrl(bFALSE);
|
||||
|
||||
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;
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
|
||||
static AD5940Err RampDacRegUpdate(uint32_t *pDACData)
|
||||
{
|
||||
uint32_t VbiasCode, VzeroCode;
|
||||
|
||||
if (AppRAMPCfg.bRampOneDir)
|
||||
{
|
||||
switch(AppRAMPCfg.RampState)
|
||||
{
|
||||
case RAMP_STATE0:
|
||||
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;
|
||||
AppRAMPCfg.CurrVzeroCode = (uint32_t)((AppRAMPCfg.VzeroPeak - 200.0f) / DAC6BITVOLT_1LSB);
|
||||
}
|
||||
break;
|
||||
case RAMP_STATE4:
|
||||
if(AppRAMPCfg.CurrStepPos >= AppRAMPCfg.StepNumber)
|
||||
AppRAMPCfg.RampState = RAMP_STOP;
|
||||
break;
|
||||
case RAMP_STOP:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(AppRAMPCfg.RampState)
|
||||
{
|
||||
case RAMP_STATE0:
|
||||
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;
|
||||
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;
|
||||
AppRAMPCfg.bDACCodeInc = AppRAMPCfg.bDACCodeInc ? bFALSE : bTRUE;
|
||||
}
|
||||
break;
|
||||
case RAMP_STATE3:
|
||||
if(AppRAMPCfg.CurrStepPos >= (AppRAMPCfg.StepNumber * 3) / 4)
|
||||
{
|
||||
AppRAMPCfg.RampState = RAMP_STATE4;
|
||||
AppRAMPCfg.CurrVzeroCode = (uint32_t)((AppRAMPCfg.VzeroStart - 200.0f) / DAC6BITVOLT_1LSB);
|
||||
}
|
||||
break;
|
||||
case RAMP_STATE4:
|
||||
if(AppRAMPCfg.CurrStepPos >= AppRAMPCfg.StepNumber)
|
||||
AppRAMPCfg.RampState = RAMP_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 --;
|
||||
|
||||
if(VbiasCode > 4095) VbiasCode = 4095;
|
||||
if(VzeroCode > 63) VzeroCode = 63;
|
||||
*pDACData = (VzeroCode << 12) | VbiasCode;
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
|
||||
static AD5940Err AppRAMPSeqDACCtrlGen(void)
|
||||
{
|
||||
#define SEQLEN_ONESTEP 4L
|
||||
#define CURRBLK_BLK0 0
|
||||
#define CURRBLK_BLK1 1
|
||||
AD5940Err error = AD5940ERR_OK;
|
||||
uint32_t BlockStartSRAMAddr;
|
||||
uint32_t DACData, SRAMAddr;
|
||||
uint32_t i;
|
||||
uint32_t StepsThisBlock;
|
||||
BoolFlag bIsFinalBlk;
|
||||
uint32_t SeqCmdBuff[SEQLEN_ONESTEP];
|
||||
|
||||
static BoolFlag bCmdForSeq0 = bTRUE;
|
||||
static uint32_t DACSeqBlk0Addr, DACSeqBlk1Addr;
|
||||
static uint32_t StepsRemainning, StepsPerBlock, DACSeqCurrBlk;
|
||||
|
||||
if(AppRAMPCfg.bFirstDACSeq == bTRUE)
|
||||
{
|
||||
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;
|
||||
DACSeqLenMax -= SEQLEN_ONESTEP * 2;
|
||||
StepsPerBlock = DACSeqLenMax / SEQLEN_ONESTEP / 2;
|
||||
DACSeqBlk0Addr = AppRAMPCfg.ADCSeqInfo.SeqRamAddr + AppRAMPCfg.ADCSeqInfo.SeqLen;
|
||||
DACSeqBlk1Addr = DACSeqBlk0Addr + StepsPerBlock * SEQLEN_ONESTEP;
|
||||
DACSeqCurrBlk = CURRBLK_BLK0;
|
||||
|
||||
if (AppRAMPCfg.bRampOneDir)
|
||||
{
|
||||
AppRAMPCfg.DACCodePerStep = ((AppRAMPCfg.RampPeakVolt - AppRAMPCfg.RampStartVolt) / AppRAMPCfg.StepNumber) / DAC12BITVOLT_1LSB;
|
||||
}
|
||||
else
|
||||
{
|
||||
AppRAMPCfg.DACCodePerStep = ((AppRAMPCfg.RampPeakVolt - AppRAMPCfg.RampStartVolt) / AppRAMPCfg.StepNumber * 2) / DAC12BITVOLT_1LSB;
|
||||
}
|
||||
|
||||
if(AppRAMPCfg.DACCodePerStep > 0)
|
||||
AppRAMPCfg.bDACCodeInc = bTRUE;
|
||||
else
|
||||
{
|
||||
AppRAMPCfg.DACCodePerStep = -AppRAMPCfg.DACCodePerStep;
|
||||
AppRAMPCfg.bDACCodeInc = bFALSE;
|
||||
}
|
||||
AppRAMPCfg.CurrRampCode = AppRAMPCfg.RampStartVolt / DAC12BITVOLT_1LSB;
|
||||
|
||||
AppRAMPCfg.RampState = RAMP_STATE0;
|
||||
AppRAMPCfg.CurrStepPos = 0;
|
||||
|
||||
bCmdForSeq0 = bTRUE;
|
||||
}
|
||||
|
||||
if(StepsRemainning == 0) return AD5940ERR_OK;
|
||||
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;
|
||||
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_SLP();
|
||||
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
|
||||
bCmdForSeq0 = bCmdForSeq0 ? bFALSE : bTRUE;
|
||||
}
|
||||
|
||||
if(bIsFinalBlk)
|
||||
{
|
||||
uint32_t CurrAddr = SRAMAddr;
|
||||
SRAMAddr += SEQLEN_ONESTEP;
|
||||
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_SLP();
|
||||
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
|
||||
CurrAddr += SEQLEN_ONESTEP;
|
||||
|
||||
SeqCmdBuff[0] = SEQ_NOP();
|
||||
SeqCmdBuff[1] = SEQ_NOP();
|
||||
SeqCmdBuff[2] = SEQ_NOP();
|
||||
SeqCmdBuff[3] = SEQ_STOP();
|
||||
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
|
||||
}
|
||||
else
|
||||
{
|
||||
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();
|
||||
AD5940_SEQCmdWrite(CurrAddr, SeqCmdBuff, SEQLEN_ONESTEP);
|
||||
bCmdForSeq0 = bCmdForSeq0 ? bFALSE : bTRUE;
|
||||
}
|
||||
|
||||
DACSeqCurrBlk = (DACSeqCurrBlk == CURRBLK_BLK0) ? CURRBLK_BLK1 : CURRBLK_BLK0;
|
||||
if(AppRAMPCfg.bFirstDACSeq)
|
||||
{
|
||||
AppRAMPCfg.bFirstDACSeq = bFALSE;
|
||||
if(bIsFinalBlk == bFALSE)
|
||||
{
|
||||
error = AppRAMPSeqDACCtrlGen();
|
||||
if(error != AD5940ERR_OK) return error;
|
||||
}
|
||||
AppRAMPCfg.DACSeqInfo.SeqId = SEQID_0;
|
||||
AppRAMPCfg.DACSeqInfo.SeqLen = SEQLEN_ONESTEP;
|
||||
AppRAMPCfg.DACSeqInfo.SeqRamAddr = BlockStartSRAMAddr;
|
||||
AppRAMPCfg.DACSeqInfo.WriteSRAM = bFALSE;
|
||||
AD5940_SEQInfoCfg(&AppRAMPCfg.DACSeqInfo);
|
||||
}
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
|
||||
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) return AD5940ERR_WAKEUP;
|
||||
|
||||
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);
|
||||
|
||||
if((AppRAMPCfg.RAMPInited == bFALSE) || (AppRAMPCfg.bParaChanged == bTRUE))
|
||||
{
|
||||
if(pBuffer == 0) return AD5940ERR_PARA;
|
||||
if(BufferSize == 0) return AD5940ERR_PARA;
|
||||
|
||||
AppRAMPCfg.RAMPInited = bFALSE;
|
||||
AD5940_SEQGenInit(pBuffer, BufferSize);
|
||||
|
||||
error = AppRAMPSeqInitGen();
|
||||
if(error != AD5940ERR_OK) return error;
|
||||
error = AppRAMPSeqADCCtrlGen();
|
||||
if(error != AD5940ERR_OK) return error;
|
||||
AppRAMPCfg.bParaChanged = bFALSE;
|
||||
}
|
||||
|
||||
AD5940_FIFOCtrlS(FIFOSRC_SINC3, bFALSE);
|
||||
fifo_cfg.FIFOEn = bTRUE;
|
||||
fifo_cfg.FIFOSrc = FIFOSRC_SINC3;
|
||||
fifo_cfg.FIFOThresh = AppRAMPCfg.FifoThresh;
|
||||
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
|
||||
fifo_cfg.FIFOSize = FIFOSIZE_2KB;
|
||||
AD5940_FIFOCfg(&fifo_cfg);
|
||||
|
||||
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
|
||||
|
||||
AppRAMPCfg.bFirstDACSeq = bTRUE;
|
||||
error = AppRAMPSeqDACCtrlGen();
|
||||
if(error != AD5940ERR_OK) return error;
|
||||
|
||||
AppRAMPCfg.InitSeqInfo.WriteSRAM = bFALSE;
|
||||
AD5940_SEQInfoCfg(&AppRAMPCfg.InitSeqInfo);
|
||||
|
||||
AD5940_SEQCtrlS(bTRUE);
|
||||
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);
|
||||
AD5940_ClrMCUIntFlag();
|
||||
|
||||
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ);
|
||||
|
||||
AppRAMPCfg.RAMPInited = bTRUE;
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
|
||||
static int32_t AppRAMPRegModify(int32_t *const pData, uint32_t *pDataCount)
|
||||
{
|
||||
if(AppRAMPCfg.StopRequired == bTRUE)
|
||||
{
|
||||
AD5940_WUPTCtrl(bFALSE);
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
AD5940Err AppRAMPISR(void *pBuff, uint32_t *pCount)
|
||||
{
|
||||
uint32_t BuffCount;
|
||||
uint32_t FifoCnt;
|
||||
BuffCount = *pCount;
|
||||
uint32_t IntFlag;
|
||||
|
||||
if(AD5940_WakeUp(10) > 10) return AD5940ERR_WAKEUP;
|
||||
AD5940_SleepKeyCtrlS(SLPKEY_LOCK);
|
||||
*pCount = 0;
|
||||
IntFlag = AD5940_INTCGetFlag(AFEINTC_0);
|
||||
|
||||
if(IntFlag & AFEINTSRC_CUSTOMINT0)
|
||||
{
|
||||
AD5940Err error;
|
||||
AD5940_INTCClrFlag(AFEINTSRC_CUSTOMINT0);
|
||||
error = AppRAMPSeqDACCtrlGen();
|
||||
if(error != AD5940ERR_OK) return error;
|
||||
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
|
||||
}
|
||||
if(IntFlag & AFEINTSRC_DATAFIFOTHRESH)
|
||||
{
|
||||
FifoCnt = AD5940_FIFOGetCnt();
|
||||
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
|
||||
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
|
||||
AppRAMPRegModify((int32_t*)pBuff, &FifoCnt);
|
||||
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
|
||||
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);
|
||||
AppRAMPDataProcess((int32_t *)pBuff, &FifoCnt);
|
||||
*pCount = FifoCnt;
|
||||
AppRAMPCtrl(APPCTRL_STOPNOW, 0);
|
||||
|
||||
// Signal that we are done
|
||||
return AD5940ERR_STOP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
// File: RampTest.h
|
||||
#ifndef _RAMPTEST_H_
|
||||
#define _RAMPTEST_H_
|
||||
#include "ad5940.h"
|
||||
#include <stdio.h>
|
||||
#include "string.h"
|
||||
#include "math.h"
|
||||
|
||||
#define ALIGIN_VOLT2LSB 0
|
||||
#define DAC12BITVOLT_1LSB (2200.0f/4095) //mV
|
||||
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB*64) //mV
|
||||
|
||||
typedef struct
|
||||
{
|
||||
BoolFlag bParaChanged;
|
||||
uint32_t SeqStartAddr;
|
||||
uint32_t MaxSeqLen;
|
||||
uint32_t SeqStartAddrCal;
|
||||
uint32_t MaxSeqLenCal;
|
||||
float LFOSCClkFreq;
|
||||
float SysClkFreq;
|
||||
float AdcClkFreq;
|
||||
float RcalVal;
|
||||
float ADCRefVolt;
|
||||
BoolFlag bTestFinished;
|
||||
float RampStartVolt;
|
||||
float RampPeakVolt;
|
||||
float VzeroStart;
|
||||
float VzeroPeak;
|
||||
uint32_t StepNumber;
|
||||
uint32_t RampDuration;
|
||||
float SampleDelay;
|
||||
uint32_t LPTIARtiaSel;
|
||||
uint32_t LPTIARloadSel;
|
||||
uint32_t LpTiaRf; /* Added LPF Resistor Configuration */
|
||||
float ExternalRtiaValue;
|
||||
uint32_t AdcPgaGain;
|
||||
uint8_t ADCSinc3Osr;
|
||||
uint32_t FifoThresh;
|
||||
BoolFlag RAMPInited;
|
||||
fImpPol_Type RtiaValue;
|
||||
SEQInfo_Type InitSeqInfo;
|
||||
SEQInfo_Type ADCSeqInfo;
|
||||
BoolFlag bFirstDACSeq;
|
||||
SEQInfo_Type DACSeqInfo;
|
||||
uint32_t CurrStepPos;
|
||||
float DACCodePerStep;
|
||||
float CurrRampCode;
|
||||
uint32_t CurrVzeroCode;
|
||||
BoolFlag bDACCodeInc;
|
||||
BoolFlag StopRequired;
|
||||
enum _RampState{RAMP_STATE0 = 0, RAMP_STATE1, RAMP_STATE2, RAMP_STATE3, RAMP_STATE4, RAMP_STOP} RampState;
|
||||
BoolFlag bRampOneDir;
|
||||
BoolFlag ShortRe0Se0; /* Short RE0 to SE0 */
|
||||
}AppRAMPCfg_Type;
|
||||
|
||||
#define APPCTRL_START 0
|
||||
#define APPCTRL_STOPNOW 1
|
||||
#define APPCTRL_STOPSYNC 2
|
||||
#define APPCTRL_SHUTDOWN 3
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
// File: Reset.c
|
||||
#include "Reset.h"
|
||||
#include "ad5940.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/**
|
||||
* @brief Checks and prints the cause of the last reset.
|
||||
* @note The RSTSTA register is sticky. It must be cleared manually.
|
||||
*/
|
||||
void AD5940_CheckResetStatus(void)
|
||||
{
|
||||
uint32_t rststa = AD5940_ReadReg(REG_ALLON_RSTSTA);
|
||||
|
||||
// printf(">> Reset Status (0x%04X): ", rststa);
|
||||
|
||||
if(rststa & 0x01) printf("POR ");
|
||||
if(rststa & 0x02) printf("EXT ");
|
||||
if(rststa & 0x04) printf("WDT ");
|
||||
if(rststa & 0x08) printf("MMR ");
|
||||
if(rststa == 0) printf("None");
|
||||
|
||||
// printf("\n");
|
||||
|
||||
// Clear the reset status (Write 1 to clear)
|
||||
AD5940_WriteReg(REG_ALLON_RSTSTA, 0xF);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs a Software Reset (MMR Reset).
|
||||
* @return AD5940Err
|
||||
*/
|
||||
AD5940Err AD5940_SoftReset(void)
|
||||
{
|
||||
// Trigger Software Reset
|
||||
AD5940_SoftRst();
|
||||
|
||||
// Wait for the chip to reboot (essential)
|
||||
AD5940_Delay10us(100); // 1ms wait
|
||||
|
||||
// Re-initialize the AD5940 driver internal state (SPI, etc.)
|
||||
// This function is required after any reset.
|
||||
AD5940_Initialize();
|
||||
|
||||
// Check and clear the reset flag (Should show MMR)
|
||||
AD5940_CheckResetStatus();
|
||||
|
||||
return AD5940ERR_OK;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// File: Reset.h
|
||||
#ifndef _RESET_H_
|
||||
#define _RESET_H_
|
||||
|
||||
#include "ad5940.h"
|
||||
|
||||
void AD5940_CheckResetStatus(void);
|
||||
AD5940Err AD5940_SoftReset(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
# host/CMakeLists.txt
|
||||
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
project(EISConfigurator VERSION 1.0 LANGUAGES CXX C)
|
||||
|
|
@ -8,57 +10,138 @@ set(CMAKE_AUTOMOC ON)
|
|||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
# Added PrintSupport and OpenGLWidgets which are often needed by QCustomPlot
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets SerialPort PrintSupport OpenGLWidgets)
|
||||
# --- FOR WINDOWS MSVC ERRORS ---
|
||||
if(MSVC)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C>:/std:clatest>)
|
||||
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
# --- QCustomPlot Integration ---
|
||||
include(FetchContent)
|
||||
|
||||
option(BUILD_ANDROID "Build for Android" OFF)
|
||||
option(BUILD_IOS "Build for iOS" OFF)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets SerialPort PrintSupport)
|
||||
|
||||
if(ANDROID)
|
||||
find_package(Qt6 REQUIRED COMPONENTS AndroidExtras)
|
||||
endif()
|
||||
|
||||
# ==========================================
|
||||
# --- FFTW3 CONFIGURATION ---
|
||||
# ==========================================
|
||||
|
||||
if(WIN32)
|
||||
# Windows: Expects FFTW3 to be installed/found via Config
|
||||
find_package(FFTW3 CONFIG REQUIRED)
|
||||
if(TARGET FFTW3::fftw3)
|
||||
add_library(fftw3 ALIAS FFTW3::fftw3)
|
||||
endif()
|
||||
elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" AND NOT BUILD_IOS)
|
||||
message(STATUS "Detected Apple Silicon Desktop. Using Homebrew FFTW3 (Double).")
|
||||
find_library(FFTW3_LIB NAMES fftw3 libfftw3 PATHS /opt/homebrew/lib NO_DEFAULT_PATH)
|
||||
find_path(FFTW3_INCLUDE_DIR fftw3.h PATHS /opt/homebrew/include NO_DEFAULT_PATH)
|
||||
|
||||
if(NOT FFTW3_LIB OR NOT FFTW3_INCLUDE_DIR)
|
||||
message(FATAL_ERROR "FFTW3 not found in /opt/homebrew. Please run: brew install fftw")
|
||||
endif()
|
||||
|
||||
add_library(fftw3 STATIC IMPORTED)
|
||||
set_target_properties(fftw3 PROPERTIES
|
||||
IMPORTED_LOCATION "${FFTW3_LIB}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${FFTW3_INCLUDE_DIR}"
|
||||
)
|
||||
else()
|
||||
message(STATUS "Building FFTW3 from source (Double Precision)...")
|
||||
|
||||
set(ENABLE_FLOAT OFF CACHE BOOL "Build double precision" FORCE)
|
||||
set(ENABLE_SSE OFF CACHE BOOL "Disable SSE" FORCE)
|
||||
set(ENABLE_SSE2 OFF CACHE BOOL "Disable SSE2" FORCE)
|
||||
set(ENABLE_AVX OFF CACHE BOOL "Disable AVX" FORCE)
|
||||
set(ENABLE_AVX2 OFF CACHE BOOL "Disable AVX2" FORCE)
|
||||
set(ENABLE_THREADS OFF CACHE BOOL "Disable Threads" FORCE)
|
||||
set(ENABLE_OPENMP OFF CACHE BOOL "Disable OpenMP" FORCE)
|
||||
set(ENABLE_MPI OFF CACHE BOOL "Disable MPI" FORCE)
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build Static Libs" FORCE)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "Disable Tests" FORCE)
|
||||
|
||||
# Enable NEON for Android ARM64 / iOS
|
||||
if(ANDROID_ABI STREQUAL "arm64-v8a")
|
||||
message(STATUS "Enabling NEON for Android ARM64")
|
||||
set(ENABLE_NEON ON CACHE BOOL "Enable NEON" FORCE)
|
||||
elseif(BUILD_IOS)
|
||||
set(ENABLE_NEON ON CACHE BOOL "Enable NEON" FORCE)
|
||||
endif()
|
||||
|
||||
# Patch for older CMake versions inside the tarball if needed
|
||||
if(UNIX)
|
||||
set(PATCH_CMD sed -i.bak "s/cmake_minimum_required.*/cmake_minimum_required(VERSION 3.16)/" <SOURCE_DIR>/CMakeLists.txt)
|
||||
else()
|
||||
set(PATCH_CMD "")
|
||||
endif()
|
||||
|
||||
FetchContent_Declare(
|
||||
fftw3_source
|
||||
URL https://www.fftw.org/fftw-3.3.10.tar.gz
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
PATCH_COMMAND ${PATCH_CMD}
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(fftw3_source)
|
||||
endif()
|
||||
|
||||
# ==========================================
|
||||
# --- QCUSTOMPLOT ---
|
||||
# ==========================================
|
||||
FetchContent_Declare(
|
||||
QCustomPlot
|
||||
URL https://www.qcustomplot.com/release/2.1.1/QCustomPlot.tar.gz
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(QCustomPlot)
|
||||
|
||||
FetchContent_GetProperties(QCustomPlot)
|
||||
if(NOT qcustomplot_POPULATED)
|
||||
FetchContent_Populate(QCustomPlot)
|
||||
# QCustomPlot source distribution doesn't have a CMakeLists.txt
|
||||
add_library(QCustomPlot
|
||||
${qcustomplot_SOURCE_DIR}/qcustomplot.cpp
|
||||
${qcustomplot_SOURCE_DIR}/qcustomplot.h
|
||||
)
|
||||
# Link required Qt modules to the library
|
||||
target_link_libraries(QCustomPlot PUBLIC Qt6::Core Qt6::Gui Qt6::Widgets Qt6::PrintSupport)
|
||||
target_include_directories(QCustomPlot PUBLIC ${qcustomplot_SOURCE_DIR})
|
||||
endif()
|
||||
|
||||
# --- Icon Generation ---
|
||||
# ==========================================
|
||||
# --- ICON GENERATION ---
|
||||
# ==========================================
|
||||
|
||||
set(ICON_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/assets/icon_source.png")
|
||||
set(ICON_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_icons.sh")
|
||||
set(MACOS_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/app_icon.icns")
|
||||
set(WINDOWS_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/app_icon.ico")
|
||||
|
||||
# Find ImageMagick 'magick' executable
|
||||
find_program(MAGICK_EXECUTABLE NAMES magick)
|
||||
if(NOT MAGICK_EXECUTABLE)
|
||||
message(WARNING "ImageMagick 'magick' not found. Icons will not be generated.")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE)
|
||||
execute_process(COMMAND chmod +x "${ICON_SCRIPT}")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${MACOS_ICON}" "${WINDOWS_ICON}"
|
||||
COMMAND "${ICON_SCRIPT}" "${MAGICK_EXECUTABLE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
DEPENDS "${ICON_SOURCE}" "${ICON_SCRIPT}"
|
||||
COMMENT "Generating icons from source using ${MAGICK_EXECUTABLE}..."
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(GenerateIcons DEPENDS "${MACOS_ICON}" "${WINDOWS_ICON}")
|
||||
if(WIN32)
|
||||
set(WINDOWS_ICON "${CMAKE_CURRENT_BINARY_DIR}/app_icon.ico")
|
||||
set(WINDOWS_RC "${CMAKE_CURRENT_BINARY_DIR}/app_icon.rc")
|
||||
set(ICON_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_icons.bat")
|
||||
file(WRITE "${WINDOWS_RC}" "IDI_ICON1 ICON \"app_icon.ico\"\n")
|
||||
add_custom_command(
|
||||
OUTPUT "${WINDOWS_ICON}"
|
||||
COMMAND "${ICON_SCRIPT}" "${MAGICK_EXECUTABLE}" "${ICON_SOURCE}" "${WINDOWS_ICON}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
DEPENDS "${ICON_SOURCE}" "${ICON_SCRIPT}"
|
||||
COMMENT "Generating Windows Icon..."
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(GenerateIcons DEPENDS "${WINDOWS_ICON}")
|
||||
else()
|
||||
set(MACOS_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/app_icon.icns")
|
||||
set(IOS_ASSETS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/ios/Assets.xcassets")
|
||||
set(IOS_CONTENTS_JSON "${IOS_ASSETS_PATH}/Contents.json")
|
||||
set(ANDROID_RES_PATH "${CMAKE_CURRENT_SOURCE_DIR}/android/res/mipmap-mdpi/ic_launcher.png")
|
||||
set(ICON_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_icons.sh")
|
||||
execute_process(COMMAND chmod +x "${ICON_SCRIPT}")
|
||||
add_custom_command(
|
||||
OUTPUT "${MACOS_ICON}" "${IOS_CONTENTS_JSON}" "${ANDROID_RES_PATH}"
|
||||
COMMAND "${ICON_SCRIPT}" "${MAGICK_EXECUTABLE}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
DEPENDS "${ICON_SOURCE}" "${ICON_SCRIPT}"
|
||||
COMMENT "Generating Cross-Platform Icons..."
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(GenerateIcons DEPENDS "${MACOS_ICON}" "${IOS_CONTENTS_JSON}" "${ANDROID_RES_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# --- Sources ---
|
||||
|
|
@ -66,39 +149,91 @@ endif()
|
|||
set(PROJECT_SOURCES
|
||||
src/main.cpp
|
||||
src/MainWindow.cpp
|
||||
src/MainWindow_UI.cpp
|
||||
src/MainWindow_Actions.cpp
|
||||
src/MainWindow_Serial.cpp
|
||||
src/GraphWidget.cpp
|
||||
${qcustomplot_SOURCE_DIR}/qcustomplot.cpp
|
||||
)
|
||||
|
||||
if(EXISTS "${ICON_SOURCE}")
|
||||
list(APPEND PROJECT_SOURCES ${MACOS_ICON} ${WINDOWS_ICON})
|
||||
if(WIN32)
|
||||
list(APPEND PROJECT_SOURCES ${WINDOWS_RC} ${WINDOWS_ICON})
|
||||
elseif(APPLE AND NOT BUILD_IOS)
|
||||
list(APPEND PROJECT_SOURCES ${MACOS_ICON})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(PROJECT_HEADERS
|
||||
src/MainWindow.h
|
||||
src/GraphWidget.h
|
||||
${qcustomplot_SOURCE_DIR}/qcustomplot.h
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
add_library(EISConfigurator SHARED ${PROJECT_SOURCES} ${PROJECT_HEADERS})
|
||||
else()
|
||||
# Removed MANUAL_FINALIZATION keyword as add_executable does not support it
|
||||
add_executable(EISConfigurator ${PROJECT_SOURCES} ${PROJECT_HEADERS})
|
||||
endif()
|
||||
# Use qt_add_executable for proper Android/iOS handling
|
||||
qt_add_executable(EISConfigurator MANUAL_FINALIZATION ${PROJECT_SOURCES} ${PROJECT_HEADERS})
|
||||
|
||||
if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE)
|
||||
add_dependencies(EISConfigurator GenerateIcons)
|
||||
endif()
|
||||
|
||||
target_link_libraries(EISConfigurator PRIVATE
|
||||
Qt6::Core Qt6::Gui Qt6::Widgets Qt6::SerialPort
|
||||
QCustomPlot
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
set_target_properties(EISConfigurator PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
# --- Mobile Definitions ---
|
||||
if(BUILD_ANDROID OR BUILD_IOS)
|
||||
target_compile_definitions(EISConfigurator PRIVATE IS_MOBILE)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
# --- Linking ---
|
||||
|
||||
# Handle FFTW3 Linking and Include Paths
|
||||
if(TARGET fftw3)
|
||||
set(FFTW_TARGET fftw3)
|
||||
# If built from source via FetchContent, we need to manually add include dirs
|
||||
if(DEFINED fftw3_source_SOURCE_DIR)
|
||||
target_include_directories(EISConfigurator PRIVATE
|
||||
"${fftw3_source_SOURCE_DIR}/api"
|
||||
"${fftw3_source_BINARY_DIR}"
|
||||
)
|
||||
endif()
|
||||
else()
|
||||
# Fallback if target isn't defined (shouldn't happen with above logic)
|
||||
set(FFTW_TARGET fftw3)
|
||||
endif()
|
||||
|
||||
target_include_directories(EISConfigurator PRIVATE ${qcustomplot_SOURCE_DIR})
|
||||
|
||||
|
||||
if(BUILD_ANDROID)
|
||||
target_link_libraries(EISConfigurator PRIVATE log m)
|
||||
set_property(TARGET EISConfigurator PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
|
||||
target_link_libraries(EISConfigurator PRIVATE Qt6::Widgets Qt6::SerialPort Qt6::AndroidExtras)
|
||||
else()
|
||||
add_executable(EISConfigurator ${PROJECT_SOURCES})
|
||||
target_link_libraries(EISConfigurator PRIVATE Qt6::Widgets Qt6::SerialPort)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
|
||||
if(BUILD_IOS)
|
||||
if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/Info.plist")
|
||||
message(FATAL_ERROR "Missing ios/Info.plist. Please create it before building.")
|
||||
endif()
|
||||
set_target_properties(EISConfigurator PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "EIS Configurator"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "com.eis.configurator"
|
||||
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/Info.plist"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
RESOURCE "${IOS_ASSETS_PATH}"
|
||||
)
|
||||
if(EXISTS "${ICON_SOURCE}")
|
||||
file(MAKE_DIRECTORY "${IOS_ASSETS_PATH}")
|
||||
target_sources(EISConfigurator PRIVATE "${IOS_ASSETS_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE AND NOT BUILD_IOS)
|
||||
set_target_properties(EISConfigurator PROPERTIES
|
||||
MACOSX_BUNDLE TRUE
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "EIS Configurator"
|
||||
|
|
@ -110,13 +245,13 @@ elseif(WIN32)
|
|||
set_target_properties(EISConfigurator PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
if(EXISTS "${WINDOWS_ICON}")
|
||||
set_target_properties(EISConfigurator PROPERTIES
|
||||
WIN32_ICON "${WINDOWS_ICON}"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(EISConfigurator PRIVATE
|
||||
Qt6::Core Qt6::Gui Qt6::Widgets Qt6::SerialPort Qt6::PrintSupport
|
||||
${FFTW_TARGET}
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
qt_finalize_executable(EISConfigurator)
|
||||
endif()
|
||||
# CRITICAL: Triggers androiddeployqt to build the APK
|
||||
qt_finalize_executable(EISConfigurator)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Makefile
|
||||
# host/Makefile
|
||||
|
||||
QT_ANDROID_KIT ?= $(HOME)/Qt/6.8.3/android_arm64_v8a
|
||||
QT_IOS_KIT ?= $(HOME)/Qt/6.8.3/ios
|
||||
|
|
@ -13,7 +13,7 @@ TARGET = EISConfigurator
|
|||
|
||||
# Android Specifics
|
||||
PKG_NAME = org.qtproject.example.EISConfigurator
|
||||
# CRITICAL FIX: Qt6 generates 'android-build-debug.apk' by default
|
||||
# Qt6 generates 'android-build-debug.apk' by default
|
||||
APK_PATH = $(BUILD_DIR_ANDROID)/android-build/build/outputs/apk/debug/android-build-debug.apk
|
||||
|
||||
all: macos
|
||||
|
|
|
|||
|
|
@ -1,21 +1,26 @@
|
|||
<?xml version="1.0"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.qtproject.example.YrCrystals"
|
||||
package="org.qtproject.example.EISConfigurator"
|
||||
android:versionName="1.0"
|
||||
android:versionCode="1"
|
||||
android:installLocation="auto">
|
||||
|
||||
<!-- USB Host is required for Serial Port communication -->
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="true" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<application android:label="Yr Crystals"
|
||||
<application android:label="EIS Configurator"
|
||||
android:name="org.qtproject.qt.android.bindings.QtApplication"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:extractNativeLibs="true">
|
||||
|
||||
<activity android:name="org.qtproject.qt.android.bindings.QtActivity"
|
||||
android:label="Yr Crystals"
|
||||
android:label="EIS Configurator"
|
||||
android:screenOrientation="unspecified"
|
||||
android:launchMode="singleTop"
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
|
||||
|
|
@ -26,7 +31,12 @@
|
|||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="YrCrystals"/>
|
||||
<!-- USB Device Attachment Intent (Optional: Auto-open app on plug) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="EISConfigurator"/>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
@ -1,21 +1,21 @@
|
|||
# scripts/generate_icons.sh
|
||||
# host/scripts/generate_icons.sh
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Argument 1: Path to magick executable
|
||||
# Resolve the directory where the script is located
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
# Project Root is two levels up (host/scripts -> host -> root)
|
||||
PROJECT_ROOT="$SCRIPT_DIR/../.."
|
||||
|
||||
MAGICK_BIN="$1"
|
||||
if [ -z "$MAGICK_BIN" ]; then MAGICK_BIN="magick"; fi
|
||||
|
||||
# Fallback if not provided
|
||||
if [ -z "$MAGICK_BIN" ]; then
|
||||
MAGICK_BIN="magick"
|
||||
fi
|
||||
|
||||
# Assumes running from Project Root
|
||||
SOURCE="assets/icon_source.png"
|
||||
OUT_DIR="assets/icons"
|
||||
SOURCE="$PROJECT_ROOT/assets/icon_source.png"
|
||||
OUT_DIR="$PROJECT_ROOT/assets/icons"
|
||||
ANDROID_RES_DIR="$PROJECT_ROOT/host/android/res"
|
||||
|
||||
if [ ! -f "$SOURCE" ]; then
|
||||
echo "Error: Source image '$SOURCE' not found in $(pwd)"
|
||||
echo "Error: Source image '$SOURCE' not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -57,22 +57,22 @@ rm -rf "$ICONSET"
|
|||
-delete 0 -alpha off -colors 256 "$OUT_DIR/app_icon.ico"
|
||||
|
||||
# Android
|
||||
ANDROID_DIR="$OUT_DIR/android/res"
|
||||
echo "Generating Android Icons into $ANDROID_RES_DIR..."
|
||||
|
||||
mkdir -p "$ANDROID_DIR/mipmap-mdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 48x48 "$ANDROID_DIR/mipmap-mdpi/ic_launcher.png"
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-mdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 48x48 "$ANDROID_RES_DIR/mipmap-mdpi/ic_launcher.png"
|
||||
|
||||
mkdir -p "$ANDROID_DIR/mipmap-hdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 72x72 "$ANDROID_DIR/mipmap-hdpi/ic_launcher.png"
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-hdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 72x72 "$ANDROID_RES_DIR/mipmap-hdpi/ic_launcher.png"
|
||||
|
||||
mkdir -p "$ANDROID_DIR/mipmap-xhdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 96x96 "$ANDROID_DIR/mipmap-xhdpi/ic_launcher.png"
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xhdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 96x96 "$ANDROID_RES_DIR/mipmap-xhdpi/ic_launcher.png"
|
||||
|
||||
mkdir -p "$ANDROID_DIR/mipmap-xxhdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 144x144 "$ANDROID_DIR/mipmap-xxhdpi/ic_launcher.png"
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xxhdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 144x144 "$ANDROID_RES_DIR/mipmap-xxhdpi/ic_launcher.png"
|
||||
|
||||
mkdir -p "$ANDROID_DIR/mipmap-xxxhdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 192x192 "$ANDROID_DIR/mipmap-xxxhdpi/ic_launcher.png"
|
||||
mkdir -p "$ANDROID_RES_DIR/mipmap-xxxhdpi"
|
||||
"$MAGICK_BIN" "$SOURCE" -scale 192x192 "$ANDROID_RES_DIR/mipmap-xxxhdpi/ic_launcher.png"
|
||||
|
||||
# iOS
|
||||
XCASSETS_DIR="$OUT_DIR/ios/Assets.xcassets"
|
||||
|
|
|
|||
|
|
@ -2,164 +2,483 @@
|
|||
#include "GraphWidget.h"
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include <QMenu>
|
||||
#include <QInputDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
GraphWidget::GraphWidget(QWidget *parent) : QWidget(parent) {
|
||||
layout = new QVBoxLayout(this);
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
toolLayout->addWidget(btnScaleX);
|
||||
toolLayout->addWidget(btnScaleY);
|
||||
toolLayout->addWidget(btnScaleBoth);
|
||||
toolLayout->addWidget(btnCenter);
|
||||
toolLayout->addStretch();
|
||||
toolLayout->addWidget(btnAnalyze);
|
||||
|
||||
mainLayout->addWidget(toolbar);
|
||||
|
||||
plot = new QCustomPlot(this);
|
||||
mainLayout->addWidget(plot);
|
||||
|
||||
// Setup Layout
|
||||
layout->addWidget(plot);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
plot->setBackground(QBrush(QColor(25, 25, 25)));
|
||||
|
||||
// Setup Graphs
|
||||
graph1 = plot->addGraph();
|
||||
graph2 = plot->addGraph(plot->xAxis, plot->yAxis2);
|
||||
graph3 = plot->addGraph(plot->xAxis, plot->yAxis2); // Hilbert Trace
|
||||
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));
|
||||
};
|
||||
|
||||
// Style Graph 1 (Real - Blue)
|
||||
QPen pen1(Qt::blue);
|
||||
pen1.setWidth(2);
|
||||
graph1->setPen(pen1);
|
||||
graph1->setLineStyle(QCPGraph::lsLine);
|
||||
graph1->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 3));
|
||||
graph1->setName("Real");
|
||||
styleAxis(plot->xAxis);
|
||||
styleAxis(plot->yAxis);
|
||||
styleAxis(plot->yAxis2);
|
||||
|
||||
// Style Graph 2 (Imaginary - Red)
|
||||
QPen pen2(Qt::red);
|
||||
pen2.setWidth(2);
|
||||
graph2->setPen(pen2);
|
||||
graph2->setLineStyle(QCPGraph::lsLine);
|
||||
graph2->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssTriangle, 3));
|
||||
graph2->setName("Imaginary");
|
||||
// --- 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));
|
||||
|
||||
// Style Graph 3 (Hilbert - Green Dashed)
|
||||
QPen pen3(Qt::green);
|
||||
pen3.setWidth(2);
|
||||
pen3.setStyle(Qt::DashLine);
|
||||
graph3->setPen(pen3);
|
||||
graph3->setLineStyle(QCPGraph::lsLine);
|
||||
graph3->setName("Hilbert (Analytic)");
|
||||
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);
|
||||
|
||||
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");
|
||||
|
||||
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)");
|
||||
|
||||
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)");
|
||||
|
||||
graphFit = plot->addGraph();
|
||||
graphFit->setPen(QPen(Qt::red, 2));
|
||||
graphFit->setName("Fit");
|
||||
|
||||
graphNyquistRaw = graphReal;
|
||||
|
||||
// Enable Right Axis
|
||||
plot->yAxis2->setVisible(true);
|
||||
plot->yAxis2->setTickLabels(true);
|
||||
|
||||
// Link Axes for Zooming
|
||||
connect(plot->yAxis, SIGNAL(rangeChanged(QCPRange)), plot->yAxis2, SLOT(setRange(QCPRange)));
|
||||
connect(plot->yAxis2, SIGNAL(rangeChanged(QCPRange)), plot->yAxis, SLOT(setRange(QCPRange)));
|
||||
|
||||
// Interactions
|
||||
plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
|
||||
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);
|
||||
|
||||
// Legend
|
||||
plot->legend->setVisible(true);
|
||||
QFont legendFont = font();
|
||||
legendFont.setPointSize(9);
|
||||
plot->legend->setFont(legendFont);
|
||||
plot->legend->setBrush(QBrush(QColor(255, 255, 255, 230)));
|
||||
plot->legend->setBrush(QBrush(QColor(40, 40, 40, 200)));
|
||||
plot->legend->setBorderPen(QPen(Qt::white));
|
||||
plot->legend->setTextColor(Qt::white);
|
||||
|
||||
// Default Mode
|
||||
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);
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void GraphWidget::configureRawPlot() {
|
||||
clear();
|
||||
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());
|
||||
}
|
||||
|
||||
void GraphWidget::performFit(const QVector<double>& x, const QVector<double>& y, int type, int order, bool inverse) {
|
||||
graphFit->data()->clear();
|
||||
QVector<double> xFit, yFit;
|
||||
|
||||
// X Axis: Frequency (Log)
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
// 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");
|
||||
|
||||
// Y Axis 1: Real (Linear)
|
||||
plot->yAxis->setLabel("Real (Ohms)");
|
||||
plot->yAxis->setScaleType(QCPAxis::stLinear);
|
||||
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
|
||||
plot->yAxis->setTicker(linTicker);
|
||||
plot->yAxis->setNumberFormat("f");
|
||||
|
||||
// Y Axis 2: Imaginary (Linear)
|
||||
plot->yAxis2->setLabel("Imaginary (Ohms)");
|
||||
plot->yAxis2->setScaleType(QCPAxis::stLinear);
|
||||
plot->yAxis2->setTicker(linTicker);
|
||||
plot->yAxis2->setNumberFormat("f");
|
||||
plot->yAxis2->setVisible(true);
|
||||
|
||||
graph1->setName("Real");
|
||||
graph2->setName("Imaginary");
|
||||
graph2->setVisible(true);
|
||||
graph3->setVisible(true);
|
||||
graphReal->setName("Real");
|
||||
graphImag->setName("Imaginary");
|
||||
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);
|
||||
|
||||
plot->replot();
|
||||
}
|
||||
|
||||
void GraphWidget::configureNyquistPlot() {
|
||||
clear();
|
||||
|
||||
// X Axis: Real (Z')
|
||||
plot->xAxis->setLabel("Real (Z')");
|
||||
plot->xAxis->setScaleType(QCPAxis::stLinear);
|
||||
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
|
||||
plot->xAxis->setTicker(linTicker);
|
||||
plot->xAxis->setNumberFormat("f");
|
||||
|
||||
// Y Axis 1: -Imaginary (-Z'')
|
||||
plot->yAxis->setLabel("-Imaginary (-Z'')");
|
||||
plot->yAxis->setScaleType(QCPAxis::stLinear);
|
||||
plot->yAxis->setTicker(linTicker);
|
||||
plot->yAxis->setNumberFormat("f");
|
||||
|
||||
// Disable Secondary Axis for Nyquist
|
||||
plot->yAxis2->setVisible(false);
|
||||
graph2->setVisible(false);
|
||||
graph3->setVisible(false);
|
||||
|
||||
graph1->setName("Nyquist");
|
||||
graph1->setLineStyle(QCPGraph::lsLine);
|
||||
graph1->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);
|
||||
|
||||
plot->replot();
|
||||
}
|
||||
|
||||
void GraphWidget::addData(double x, double val1, double val2) {
|
||||
// Mode Detection based on Axis Labels (Simple state check)
|
||||
bool isNyquist = (plot->xAxis->label() == "Real (Z')");
|
||||
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");
|
||||
|
||||
if (isNyquist) {
|
||||
// For Nyquist: x = Real, val1 = Imaginary
|
||||
// We plot Real vs -Imaginary
|
||||
graph1->addData(x, -val1);
|
||||
} else {
|
||||
// Raw Plot: x = Freq, val1 = Real, val2 = Imag
|
||||
if (plot->xAxis->scaleType() == QCPAxis::stLogarithmic && x <= 0) return;
|
||||
|
||||
graph1->addData(x, val1);
|
||||
graph2->addData(x, val2);
|
||||
plot->yAxis->setLabel("Current (uA)");
|
||||
plot->yAxis->setScaleType(QCPAxis::stLinear);
|
||||
plot->yAxis->setTicker(linTicker);
|
||||
plot->yAxis->setNumberFormat("f");
|
||||
|
||||
plot->yAxis2->setVisible(false);
|
||||
|
||||
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);
|
||||
|
||||
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->yAxis->setLabel("Current (uA)");
|
||||
plot->yAxis->setScaleType(QCPAxis::stLinear);
|
||||
plot->yAxis->setTicker(linTicker);
|
||||
plot->yAxis->setNumberFormat("f");
|
||||
|
||||
plot->yAxis2->setVisible(false);
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// Auto-scale
|
||||
graph1->rescaleAxes(false);
|
||||
if (!isNyquist) {
|
||||
graph2->rescaleAxes(false);
|
||||
graph3->rescaleAxes(false);
|
||||
void GraphWidget::addAmperometricData(double index, double current) {
|
||||
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;
|
||||
}
|
||||
|
||||
plot->replot();
|
||||
if (target) {
|
||||
target->addData(voltage, current);
|
||||
target->rescaleAxes(false);
|
||||
plot->replot();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphWidget::addHilbertData(const QVector<double>& freq, const QVector<double>& hilbertImag) {
|
||||
// Only valid for Raw Plot
|
||||
if (plot->xAxis->label() != "Frequency (Hz)") return;
|
||||
graphHilbert->setData(freq, hilbertImag);
|
||||
graphHilbert->rescaleAxes(false);
|
||||
plot->replot();
|
||||
}
|
||||
|
||||
graph3->setData(freq, hilbertImag);
|
||||
graph3->rescaleAxes(false);
|
||||
void GraphWidget::setExtrapolatedPoint(double real, double imag) {
|
||||
graphExtrapolated->data()->clear();
|
||||
graphExtrapolated->addData(real, -imag);
|
||||
plot->replot();
|
||||
}
|
||||
|
||||
void GraphWidget::clear() {
|
||||
graph1->data()->clear();
|
||||
graph2->data()->clear();
|
||||
graph3->data()->clear();
|
||||
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();
|
||||
}
|
||||
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
#include <QWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QDialog>
|
||||
#include <QComboBox>
|
||||
#include <QSpinBox>
|
||||
#include <QCheckBox>
|
||||
#include "qcustomplot.h"
|
||||
|
||||
class GraphWidget : public QWidget {
|
||||
|
|
@ -12,18 +18,61 @@ public:
|
|||
explicit GraphWidget(QWidget *parent = nullptr);
|
||||
|
||||
// Data Handling
|
||||
void addData(double freq, double val1, double val2);
|
||||
void addBodeData(double freq, double val1, double val2);
|
||||
void addNyquistData(double r_meas, double i_meas, double r_corr, double i_corr, bool showCorr);
|
||||
void addAmperometricData(double index, double current);
|
||||
|
||||
enum LSVTrace { LSV_BLANK, LSV_SAMPLE, LSV_DIFF };
|
||||
void addLSVData(double voltage, double current, LSVTrace traceType);
|
||||
|
||||
void addHilbertData(const QVector<double>& freq, const QVector<double>& hilbertImag);
|
||||
void setExtrapolatedPoint(double real, double imag);
|
||||
|
||||
void clear();
|
||||
void clearLSV(LSVTrace traceType);
|
||||
|
||||
// View Configurations
|
||||
void configureRawPlot();
|
||||
void configureNyquistPlot();
|
||||
void configureAmperometricPlot();
|
||||
void configureLSVPlot();
|
||||
|
||||
private slots:
|
||||
void scaleX();
|
||||
void scaleY();
|
||||
void scaleBoth();
|
||||
void centerView();
|
||||
void startAnalyze();
|
||||
|
||||
private:
|
||||
QVBoxLayout *layout;
|
||||
QVBoxLayout *mainLayout;
|
||||
QCustomPlot *plot;
|
||||
QCPGraph *graph1; // Real
|
||||
QCPGraph *graph2; // Imaginary
|
||||
QCPGraph *graph3; // Hilbert (Analytic Imaginary)
|
||||
|
||||
// Toolbar
|
||||
QWidget *toolbar;
|
||||
QPushButton *btnScaleX;
|
||||
QPushButton *btnScaleY;
|
||||
QPushButton *btnScaleBoth;
|
||||
QPushButton *btnCenter;
|
||||
QPushButton *btnAnalyze;
|
||||
|
||||
// Graphs
|
||||
QCPGraph *graphReal;
|
||||
QCPGraph *graphImag;
|
||||
QCPGraph *graphHilbert;
|
||||
QCPGraph *graphNyquistRaw;
|
||||
QCPGraph *graphNyquistCorr;
|
||||
QCPGraph *graphExtrapolated;
|
||||
QCPGraph *graphAmp;
|
||||
QCPGraph *graphLSVBlank;
|
||||
QCPGraph *graphLSVSample;
|
||||
QCPGraph *graphLSVDiff;
|
||||
QCPGraph *graphFit; // For analysis results
|
||||
|
||||
// Analysis State
|
||||
bool isSelecting = false;
|
||||
QCPItemRect *selectionRect;
|
||||
double selStartX, selStartY;
|
||||
|
||||
void performFit(const QVector<double>& x, const QVector<double>& y, int type, int order, bool inverse);
|
||||
};
|
||||
|
|
@ -1,64 +1,25 @@
|
|||
// File: host/src/MainWindow.cpp
|
||||
#include "MainWindow.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QScroller>
|
||||
#include <QGesture>
|
||||
#include <QSplitter>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
|
||||
// Simple FFT Implementation (Cooley-Tukey)
|
||||
// Note: Size must be power of 2
|
||||
void fft(std::vector<std::complex<double>>& a) {
|
||||
int n = a.size();
|
||||
if (n <= 1) return;
|
||||
|
||||
std::vector<std::complex<double>> a0(n / 2), a1(n / 2);
|
||||
for (int i = 0; i < n / 2; i++) {
|
||||
a0[i] = a[2 * i];
|
||||
a1[i] = a[2 * i + 1];
|
||||
}
|
||||
fft(a0);
|
||||
fft(a1);
|
||||
|
||||
double ang = 2 * M_PI / n;
|
||||
std::complex<double> w(1), wn(cos(ang), sin(ang));
|
||||
for (int i = 0; i < n / 2; i++) {
|
||||
a[i] = a0[i] + w * a1[i];
|
||||
a[i + n / 2] = a0[i] - w * a1[i];
|
||||
w *= wn;
|
||||
}
|
||||
}
|
||||
|
||||
void ifft(std::vector<std::complex<double>>& a) {
|
||||
int n = a.size();
|
||||
// Conjugate
|
||||
for (int i = 0; i < n; i++) a[i] = std::conj(a[i]);
|
||||
// Forward FFT
|
||||
fft(a);
|
||||
// Conjugate again and scale
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i] = std::conj(a[i]);
|
||||
a[i] /= n;
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
settings = new QSettings("EISConfigurator", "Settings", this);
|
||||
loadSettings();
|
||||
|
||||
serial = new QSerialPort(this);
|
||||
connect(serial, &QSerialPort::readyRead, this, &MainWindow::handleSerialData);
|
||||
connect(serial, &QSerialPort::errorOccurred, this, &MainWindow::onPortError);
|
||||
|
||||
setupUi();
|
||||
// Delayed refresh to allow the window to render before auto-scanning
|
||||
blinkTimer = new QTimer(this);
|
||||
blinkTimer->setInterval(500);
|
||||
connect(blinkTimer, &QTimer::timeout, this, &MainWindow::onBlinkTimer);
|
||||
|
||||
setupUi(); // Defined in MainWindow_UI.cpp
|
||||
|
||||
// Auto-refresh ports after startup
|
||||
QTimer::singleShot(1000, this, &MainWindow::refreshPorts);
|
||||
|
||||
// Enable Swipe Gestures for Mobile Tab Switching
|
||||
grabGesture(Qt::SwipeGesture);
|
||||
}
|
||||
|
||||
|
|
@ -68,306 +29,12 @@ MainWindow::~MainWindow() {
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::setupUi() {
|
||||
// Central Layout: Splitter (Graphs Top, Log Bottom)
|
||||
QSplitter *splitter = new QSplitter(Qt::Vertical, this);
|
||||
setCentralWidget(splitter);
|
||||
|
||||
// Tab Widget for Graphs
|
||||
tabWidget = new QTabWidget(this);
|
||||
|
||||
rawGraph = new GraphWidget(this);
|
||||
rawGraph->configureRawPlot();
|
||||
|
||||
nyquistGraph = new GraphWidget(this);
|
||||
nyquistGraph->configureNyquistPlot();
|
||||
|
||||
// Raw Data is now Default (Index 0)
|
||||
tabWidget->addTab(rawGraph, "Raw Data");
|
||||
tabWidget->addTab(nyquistGraph, "Nyquist Plot");
|
||||
|
||||
// Log Widget
|
||||
logWidget = new QTextEdit(this);
|
||||
logWidget->setReadOnly(true);
|
||||
logWidget->setFont(QFont("Monospace"));
|
||||
logWidget->setPlaceholderText("Scanning for 0xCAFE EIS Device...");
|
||||
QScroller::grabGesture(logWidget->viewport(), QScroller::TouchGesture);
|
||||
|
||||
splitter->addWidget(tabWidget);
|
||||
splitter->addWidget(logWidget);
|
||||
splitter->setStretchFactor(0, 2);
|
||||
splitter->setStretchFactor(1, 1);
|
||||
|
||||
// Toolbar Construction
|
||||
toolbar = addToolBar("Connection");
|
||||
toolbar->setMovable(false);
|
||||
|
||||
portSelector = new QComboBox(this);
|
||||
portSelector->setMinimumWidth(150);
|
||||
connectBtn = new QPushButton("Connect", this);
|
||||
QPushButton *refreshBtn = new QPushButton("Refresh", this);
|
||||
|
||||
toolbar->addWidget(portSelector);
|
||||
toolbar->addWidget(connectBtn);
|
||||
toolbar->addWidget(refreshBtn);
|
||||
toolbar->addSeparator();
|
||||
|
||||
checkIdBtn = new QPushButton("Check ID", this);
|
||||
calibrateBtn = new QPushButton("Calibrate", this);
|
||||
sweepBtn = new QPushButton("Sweep", this);
|
||||
|
||||
toolbar->addWidget(checkIdBtn);
|
||||
toolbar->addWidget(calibrateBtn);
|
||||
toolbar->addWidget(sweepBtn);
|
||||
toolbar->addSeparator();
|
||||
|
||||
// Measurement Control
|
||||
QLabel *lblFreq = new QLabel(" Freq:", this);
|
||||
spinFreq = new QDoubleSpinBox(this);
|
||||
spinFreq->setRange(10.0, 200000.0);
|
||||
spinFreq->setValue(1000.0);
|
||||
spinFreq->setSuffix(" Hz");
|
||||
measureBtn = new QPushButton("Measure", this);
|
||||
|
||||
toolbar->addWidget(lblFreq);
|
||||
toolbar->addWidget(spinFreq);
|
||||
toolbar->addWidget(measureBtn);
|
||||
|
||||
checkIdBtn->setEnabled(false);
|
||||
calibrateBtn->setEnabled(false);
|
||||
sweepBtn->setEnabled(false);
|
||||
measureBtn->setEnabled(false);
|
||||
|
||||
// Signal Connections
|
||||
connect(connectBtn, &QPushButton::clicked, this, &MainWindow::connectToPort);
|
||||
connect(refreshBtn, &QPushButton::clicked, this, &MainWindow::refreshPorts);
|
||||
connect(checkIdBtn, &QPushButton::clicked, this, &MainWindow::checkDeviceId);
|
||||
connect(calibrateBtn, &QPushButton::clicked, this, &MainWindow::runCalibration);
|
||||
connect(sweepBtn, &QPushButton::clicked, this, &MainWindow::startSweep);
|
||||
connect(measureBtn, &QPushButton::clicked, this, &MainWindow::toggleMeasurement);
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
toolbar->setIconSize(QSize(32, 32));
|
||||
QFont font = portSelector->font();
|
||||
font.setPointSize(14);
|
||||
portSelector->setFont(font);
|
||||
connectBtn->setFont(font);
|
||||
refreshBtn->setFont(font);
|
||||
checkIdBtn->setFont(font);
|
||||
calibrateBtn->setFont(font);
|
||||
sweepBtn->setFont(font);
|
||||
measureBtn->setFont(font);
|
||||
spinFreq->setFont(font);
|
||||
#endif
|
||||
void MainWindow::loadSettings() {
|
||||
cellConstant = settings->value("cellConstant", 1.0).toDouble();
|
||||
}
|
||||
|
||||
void MainWindow::refreshPorts() {
|
||||
portSelector->clear();
|
||||
const auto infos = QSerialPortInfo::availablePorts();
|
||||
bool foundTarget = false;
|
||||
QString targetPort;
|
||||
|
||||
for (const QSerialPortInfo &info : infos) {
|
||||
portSelector->addItem(info.portName());
|
||||
|
||||
// Check for 0xCAFE or "usbmodem"
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-connect if found and not already connected
|
||||
if (foundTarget) {
|
||||
portSelector->setCurrentText(targetPort);
|
||||
if (!serial->isOpen()) {
|
||||
connectToPort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::connectToPort() {
|
||||
if (serial->isOpen()) {
|
||||
serial->close();
|
||||
connectBtn->setText("Connect");
|
||||
logWidget->append("--- Disconnected ---");
|
||||
checkIdBtn->setEnabled(false);
|
||||
calibrateBtn->setEnabled(false);
|
||||
sweepBtn->setEnabled(false);
|
||||
measureBtn->setEnabled(false);
|
||||
isMeasuring = false;
|
||||
measureBtn->setText("Measure");
|
||||
return;
|
||||
}
|
||||
|
||||
if (portSelector->currentText().isEmpty()) return;
|
||||
|
||||
serial->setPortName(portSelector->currentText());
|
||||
serial->setBaudRate(500000);
|
||||
|
||||
if (serial->open(QIODevice::ReadWrite)) {
|
||||
connectBtn->setText("Disconnect");
|
||||
logWidget->append("--- Connected and Synchronized ---");
|
||||
checkIdBtn->setEnabled(true);
|
||||
calibrateBtn->setEnabled(true);
|
||||
sweepBtn->setEnabled(true);
|
||||
measureBtn->setEnabled(true);
|
||||
} 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");
|
||||
checkIdBtn->setEnabled(false);
|
||||
calibrateBtn->setEnabled(false);
|
||||
sweepBtn->setEnabled(false);
|
||||
measureBtn->setEnabled(false);
|
||||
isMeasuring = false;
|
||||
measureBtn->setText("Measure");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::checkDeviceId() {
|
||||
if (serial->isOpen()) {
|
||||
logWidget->append(">> Checking ID (v)...");
|
||||
serial->write("v\n");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::runCalibration() {
|
||||
if (serial->isOpen()) {
|
||||
logWidget->append(">> Running Calibration (c)...");
|
||||
serial->write("c\n");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::startSweep() {
|
||||
if (!serial->isOpen()) return;
|
||||
|
||||
rawGraph->clear();
|
||||
nyquistGraph->clear();
|
||||
|
||||
// Clear accumulated data
|
||||
sweepFreqs.clear();
|
||||
sweepReals.clear();
|
||||
|
||||
// Use Firmware Sweep Command: s <start> <end> <steps>
|
||||
// Example: 100Hz to 200kHz, 50 steps
|
||||
logWidget->append(">> Starting Firmware Sweep (s 100 200000 50)...");
|
||||
serial->write("s 100 200000 50\n");
|
||||
}
|
||||
|
||||
void MainWindow::toggleMeasurement() {
|
||||
if (!serial->isOpen()) return;
|
||||
|
||||
if (isMeasuring) {
|
||||
// Stop
|
||||
logWidget->append(">> Stopping Measurement (x)...");
|
||||
serial->write("x\n");
|
||||
measureBtn->setText("Measure");
|
||||
isMeasuring = false;
|
||||
} else {
|
||||
// Start
|
||||
double freq = spinFreq->value();
|
||||
logWidget->append(QString(">> Requesting Measure (m %1)...").arg(freq));
|
||||
serial->write(QString("m %1\n").arg(freq).toUtf8());
|
||||
measureBtn->setText("Stop");
|
||||
isMeasuring = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::handleSerialData() {
|
||||
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);
|
||||
|
||||
if (str.startsWith("DATA,")) {
|
||||
parseData(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::parseData(const QString &data) {
|
||||
// Format: DATA,Freq,Mag,Phase,Real,Imag
|
||||
QStringList parts = data.split(',');
|
||||
if (parts.size() < 6) return;
|
||||
|
||||
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) {
|
||||
// Add to Raw Graph (Freq vs Real/Imag)
|
||||
rawGraph->addData(freq, real, imag);
|
||||
|
||||
// Add to Nyquist Graph (Real vs Imag)
|
||||
nyquistGraph->addData(real, imag, 0);
|
||||
|
||||
// Accumulate for Hilbert
|
||||
sweepFreqs.append(freq);
|
||||
sweepReals.append(real);
|
||||
|
||||
// Check if sweep is done (e.g., 50 points)
|
||||
// For now, update Hilbert every 10 points or at end
|
||||
if (sweepReals.size() >= 50) {
|
||||
computeHilbert();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::computeHilbert() {
|
||||
int n = sweepReals.size();
|
||||
if (n == 0) return;
|
||||
|
||||
// 1. Zero-pad to next power of 2
|
||||
int n_fft = 1;
|
||||
while (n_fft < n) n_fft *= 2;
|
||||
|
||||
std::vector<std::complex<double>> signal(n_fft);
|
||||
for (int i = 0; i < n; i++) {
|
||||
signal[i] = std::complex<double>(sweepReals[i], 0.0);
|
||||
}
|
||||
|
||||
// 2. FFT
|
||||
fft(signal);
|
||||
|
||||
// 3. Create Analytic Signal in Frequency Domain
|
||||
// H[0] = H[0]
|
||||
// H[i] = 2*H[i] for 0 < i < N/2
|
||||
// H[N/2] = H[N/2]
|
||||
// H[i] = 0 for N/2 < i < N
|
||||
for (int i = 1; i < n_fft / 2; i++) {
|
||||
signal[i] *= 2.0;
|
||||
}
|
||||
for (int i = n_fft / 2 + 1; i < n_fft; i++) {
|
||||
signal[i] = 0.0;
|
||||
}
|
||||
|
||||
// 4. IFFT
|
||||
ifft(signal);
|
||||
|
||||
// 5. Extract Imaginary Part (Hilbert Transform)
|
||||
QVector<double> hilbertImag;
|
||||
for (int i = 0; i < n; i++) {
|
||||
hilbertImag.append(signal[i].imag());
|
||||
}
|
||||
|
||||
// 6. Plot
|
||||
rawGraph->addHilbertData(sweepFreqs, hilbertImag);
|
||||
void MainWindow::saveSettings() {
|
||||
settings->setValue("cellConstant", cellConstant);
|
||||
}
|
||||
|
||||
bool MainWindow::event(QEvent *event) {
|
||||
|
|
@ -384,11 +51,11 @@ bool MainWindow::event(QEvent *event) {
|
|||
void MainWindow::handleSwipe(QSwipeGesture *gesture) {
|
||||
if (gesture->state() == Qt::GestureFinished) {
|
||||
if (gesture->horizontalDirection() == QSwipeGesture::Left) {
|
||||
if (tabWidget->currentIndex() < tabWidget->count() - 1)
|
||||
tabWidget->setCurrentIndex(tabWidget->currentIndex() + 1);
|
||||
if (mainTabWidget->currentIndex() < mainTabWidget->count() - 1)
|
||||
mainTabWidget->setCurrentIndex(mainTabWidget->currentIndex() + 1);
|
||||
} else if (gesture->horizontalDirection() == QSwipeGesture::Right) {
|
||||
if (tabWidget->currentIndex() > 0)
|
||||
tabWidget->setCurrentIndex(tabWidget->currentIndex() - 1);
|
||||
if (mainTabWidget->currentIndex() > 0)
|
||||
mainTabWidget->setCurrentIndex(mainTabWidget->currentIndex() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,9 @@
|
|||
#include <QGestureEvent>
|
||||
#include <QSwipeGesture>
|
||||
#include <QTimer>
|
||||
#include <QCheckBox>
|
||||
#include <QSettings>
|
||||
#include <QGroupBox>
|
||||
#include "GraphWidget.h"
|
||||
|
||||
class MainWindow : public QMainWindow {
|
||||
|
|
@ -27,44 +30,122 @@ protected:
|
|||
bool event(QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
// 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);
|
||||
|
||||
// Action Slots
|
||||
void checkDeviceId();
|
||||
void runCalibration();
|
||||
void startSweep();
|
||||
void toggleMeasurement();
|
||||
void toggleAmperometry();
|
||||
|
||||
// LSV Slots
|
||||
void startLSVBlank();
|
||||
void startLSVSample();
|
||||
void stopLSV();
|
||||
|
||||
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);
|
||||
|
||||
// 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;
|
||||
|
||||
// Views
|
||||
GraphWidget *rawGraph; // Raw Data (Freq vs Real/Imag)
|
||||
GraphWidget *nyquistGraph; // Nyquist (Real vs -Imag)
|
||||
QTextEdit *logWidget; // Serial Log
|
||||
GraphWidget *rawGraph;
|
||||
GraphWidget *nyquistGraph;
|
||||
GraphWidget *ampGraph;
|
||||
GraphWidget *lsvGraph;
|
||||
QTextEdit *logWidget;
|
||||
|
||||
// Layout
|
||||
QTabWidget *tabWidget;
|
||||
QToolBar *toolbar;
|
||||
// Layout Containers
|
||||
QTabWidget *mainTabWidget;
|
||||
QTabWidget *impGraphTabs;
|
||||
|
||||
// --- Global Controls ---
|
||||
QComboBox *portSelector;
|
||||
QPushButton *connectBtn;
|
||||
QPushButton *checkIdBtn;
|
||||
QPushButton *calibrateBtn;
|
||||
QPushButton *sweepBtn;
|
||||
QPushButton *measureBtn;
|
||||
QDoubleSpinBox *spinFreq;
|
||||
QComboBox *comboRangeLP;
|
||||
QComboBox *comboRangeHP;
|
||||
QComboBox *comboRLoad; // New Control
|
||||
QCheckBox *checkShortRe0Se0;
|
||||
|
||||
bool isMeasuring = false;
|
||||
// --- Impedance Controls ---
|
||||
QDoubleSpinBox *spinSweepStart;
|
||||
QDoubleSpinBox *spinSweepStop;
|
||||
QSpinBox *spinSweepPPD;
|
||||
QPushButton *sweepBtn;
|
||||
|
||||
// Data Accumulation for Hilbert
|
||||
QDoubleSpinBox *spinFreq;
|
||||
QPushButton *measureBtn;
|
||||
|
||||
QCheckBox *checkShunt;
|
||||
QDoubleSpinBox *spinShuntRes;
|
||||
|
||||
QDoubleSpinBox *spinCondStd;
|
||||
QPushButton *btnCalCond;
|
||||
QLabel *lblResultRs;
|
||||
QLabel *lblResultCond;
|
||||
|
||||
// --- Amperometry Controls ---
|
||||
QDoubleSpinBox *spinAmpBias;
|
||||
QPushButton *ampBtn;
|
||||
QComboBox *comboLPF;
|
||||
|
||||
// --- 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;
|
||||
|
||||
enum LSVState { LSV_IDLE, LSV_RUNNING_BLANK, LSV_RUNNING_SAMPLE };
|
||||
LSVState lsvState = LSV_IDLE;
|
||||
|
||||
// Data Accumulation
|
||||
QVector<double> sweepFreqs;
|
||||
QVector<double> sweepReals;
|
||||
QVector<double> sweepReals;
|
||||
QVector<double> sweepImags;
|
||||
|
||||
struct LSVPoint { double voltage; double current; };
|
||||
QVector<LSVPoint> lsvBlankData;
|
||||
QVector<LSVPoint> lsvSampleData;
|
||||
};
|
||||
|
|
@ -0,0 +1,394 @@
|
|||
// File: host/src/MainWindow_Actions.cpp
|
||||
#include "MainWindow.h"
|
||||
#include <complex>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <fftw3.h>
|
||||
|
||||
void MainWindow::checkDeviceId() {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onLPFChanged(int index) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::initiateSequence(const QString &cmd) {
|
||||
if (!serial->isOpen()) return;
|
||||
|
||||
pendingCommand = cmd;
|
||||
currentSequence = SEQ_WAIT_BOOT;
|
||||
|
||||
logWidget->append(">> Sequence: Resetting device (z)...");
|
||||
serial->write("z\n"); // Watchdog reboot to force clean state
|
||||
}
|
||||
|
||||
void MainWindow::startSweep() {
|
||||
if (!serial->isOpen()) return;
|
||||
|
||||
if (isSweeping) {
|
||||
// Stop Sweep
|
||||
serial->write("x\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMeasuringAmp) toggleAmperometry();
|
||||
if (lsvState != LSV_IDLE) stopLSV();
|
||||
if (isMeasuringImp) toggleMeasurement();
|
||||
|
||||
rawGraph->clear();
|
||||
nyquistGraph->clear();
|
||||
|
||||
sweepFreqs.clear();
|
||||
sweepReals.clear();
|
||||
sweepImags.clear();
|
||||
|
||||
double start = spinSweepStart->value();
|
||||
double stop = spinSweepStop->value();
|
||||
int ppd = spinSweepPPD->value();
|
||||
|
||||
QString cmd = QString("s %1 %2 %3").arg(start).arg(stop).arg(ppd);
|
||||
|
||||
isSweeping = true;
|
||||
sweepBtn->setText("Stop Sweep");
|
||||
setButtonBlinking(sweepBtn, true);
|
||||
|
||||
initiateSequence(cmd);
|
||||
}
|
||||
|
||||
void MainWindow::toggleMeasurement() {
|
||||
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);
|
||||
|
||||
measureBtn->setText("Stop");
|
||||
setButtonBlinking(measureBtn, true);
|
||||
isMeasuringImp = true;
|
||||
|
||||
initiateSequence(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::toggleAmperometry() {
|
||||
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);
|
||||
|
||||
ampBtn->setText("Stop Amp");
|
||||
setButtonBlinking(ampBtn, true);
|
||||
isMeasuringAmp = true;
|
||||
ampGraph->clear();
|
||||
|
||||
initiateSequence(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::startLSVBlank() {
|
||||
if (!serial->isOpen()) 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();
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
initiateSequence(cmd);
|
||||
}
|
||||
|
||||
void MainWindow::startLSVSample() {
|
||||
if (!serial->isOpen()) 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();
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
initiateSequence(cmd);
|
||||
}
|
||||
|
||||
void MainWindow::stopLSV() {
|
||||
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();
|
||||
}
|
||||
|
||||
lsvState = LSV_IDLE;
|
||||
}
|
||||
|
||||
void MainWindow::calculateLSVDiff() {
|
||||
if (lsvBlankData.isEmpty() || lsvSampleData.isEmpty()) return;
|
||||
|
||||
lsvGraph->clearLSV(GraphWidget::LSV_DIFF);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
logWidget->append(">> Calculated Difference Curve.");
|
||||
}
|
||||
|
||||
void MainWindow::computeHilbert() {
|
||||
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;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
double val = in[i][1] / n_fft; // Imaginary part normalized
|
||||
hilbertImag.append(val);
|
||||
}
|
||||
|
||||
rawGraph->addHilbertData(sweepFreqs, hilbertImag);
|
||||
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
|
||||
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;
|
||||
|
||||
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 ]
|
||||
|
||||
double C = sum_z / n;
|
||||
|
||||
// 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
|
||||
|
||||
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;
|
||||
|
||||
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
|
||||
|
||||
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 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));
|
||||
|
||||
double cond = (cellConstant / Rs) * 1000000.0;
|
||||
lblResultCond->setText(QString(" Cond: %1 µS/cm").arg(cond, 0, 'f', 2));
|
||||
|
||||
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 numStr = txt.section(':', 1).section(QChar(0x03A9), 0, 0).trimmed();
|
||||
double measuredRs = numStr.toDouble();
|
||||
|
||||
if (measuredRs <= 0) return;
|
||||
|
||||
double stdCond = spinCondStd->value();
|
||||
|
||||
cellConstant = (stdCond * 1e-6) * measuredRs;
|
||||
|
||||
saveSettings();
|
||||
|
||||
QMessageBox::information(this, "Calibration Success",
|
||||
QString("Cell Constant (K) calibrated to: %1 cm⁻¹").arg(cellConstant, 0, 'f', 4));
|
||||
|
||||
performCircleFit();
|
||||
}
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
// File: host/src/MainWindow_Serial.cpp
|
||||
#include "MainWindow.h"
|
||||
#include <QDateTime>
|
||||
#include <complex>
|
||||
|
||||
void MainWindow::refreshPorts() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::connectToPort() {
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
measureBtn->setText("Measure");
|
||||
ampBtn->setText("Start Amp");
|
||||
lsvBlankBtn->setText("Run Blank");
|
||||
lsvSampleBtn->setText("Run Sample");
|
||||
return;
|
||||
}
|
||||
|
||||
if (portSelector->currentText().isEmpty()) return;
|
||||
|
||||
serial->setPortName(portSelector->currentText());
|
||||
serial->setBaudRate(500000);
|
||||
|
||||
if (serial->open(QIODevice::ReadWrite)) {
|
||||
connectBtn->setText("Disconnect");
|
||||
logWidget->append("--- Connected and Synchronized ---");
|
||||
|
||||
// 1. Enable Global Controls
|
||||
checkIdBtn->setEnabled(true);
|
||||
calibrateBtn->setEnabled(true);
|
||||
comboRangeLP->setEnabled(true);
|
||||
comboRangeHP->setEnabled(true);
|
||||
comboRLoad->setEnabled(true);
|
||||
checkShortRe0Se0->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());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onPortError(QSerialPort::SerialPortError error) {
|
||||
if (error == QSerialPort::ResourceError) {
|
||||
logWidget->append(">> Critical Error: Connection Lost.");
|
||||
serial->close();
|
||||
connectBtn->setText("Connect");
|
||||
|
||||
checkIdBtn->setEnabled(false);
|
||||
calibrateBtn->setEnabled(false);
|
||||
comboRangeLP->setEnabled(false);
|
||||
comboRangeHP->setEnabled(false);
|
||||
comboRLoad->setEnabled(false);
|
||||
checkShortRe0Se0->setEnabled(false);
|
||||
|
||||
mainTabWidget->widget(0)->setEnabled(false);
|
||||
mainTabWidget->widget(1)->setEnabled(false);
|
||||
mainTabWidget->widget(2)->setEnabled(false);
|
||||
|
||||
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;
|
||||
|
||||
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...");
|
||||
|
||||
// 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 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());
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::parseData(const QString &data) {
|
||||
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] == "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] == "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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
// File: host/src/MainWindow_UI.cpp
|
||||
#include "MainWindow.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFrame>
|
||||
#include <QScroller>
|
||||
#include <QGroupBox>
|
||||
|
||||
void MainWindow::setupUi() {
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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
|
||||
|
||||
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
|
||||
|
||||
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)");
|
||||
|
||||
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);
|
||||
|
||||
mainLayout->addWidget(globalGroup);
|
||||
|
||||
// ========================================================================
|
||||
// 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);
|
||||
|
||||
// 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;");
|
||||
|
||||
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;");
|
||||
|
||||
checkShunt = new QCheckBox("Shunt");
|
||||
spinShuntRes = new QDoubleSpinBox(); spinShuntRes->setRange(1.0, 1000000.0); spinShuntRes->setValue(466.0); spinShuntRes->setSuffix(" Ω");
|
||||
|
||||
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;");
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
impLayout->addWidget(impCtrlGroup);
|
||||
|
||||
// Graphs (Nested Tab)
|
||||
impGraphTabs = new QTabWidget();
|
||||
rawGraph = new GraphWidget(this);
|
||||
rawGraph->configureRawPlot();
|
||||
nyquistGraph = new GraphWidget(this);
|
||||
nyquistGraph->configureNyquistPlot();
|
||||
|
||||
impGraphTabs->addTab(rawGraph, "Bode Plot");
|
||||
impGraphTabs->addTab(nyquistGraph, "Nyquist Plot");
|
||||
impLayout->addWidget(impGraphTabs);
|
||||
|
||||
mainTabWidget->addTab(impTab, "Impedance");
|
||||
|
||||
// --- Tab 2: Amperometry ---
|
||||
QWidget *ampTab = new QWidget();
|
||||
QVBoxLayout *ampLayout = new QVBoxLayout(ampTab);
|
||||
ampLayout->setContentsMargins(4, 4, 4, 4);
|
||||
|
||||
QGroupBox *ampCtrlGroup = new QGroupBox("Amperometry Controls", ampTab);
|
||||
QHBoxLayout *ampCtrlLayout = new QHBoxLayout(ampCtrlGroup);
|
||||
|
||||
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);
|
||||
|
||||
ampBtn = new QPushButton("Start Amp");
|
||||
ampBtn->setStyleSheet("background-color: rgba(238, 130, 238, 51); color: white; font-weight: bold;");
|
||||
|
||||
ampCtrlLayout->addWidget(new QLabel("Bias:")); ampCtrlLayout->addWidget(spinAmpBias);
|
||||
ampCtrlLayout->addWidget(new QLabel("LPF:")); ampCtrlLayout->addWidget(comboLPF);
|
||||
ampCtrlLayout->addWidget(ampBtn);
|
||||
|
||||
ampLayout->addWidget(ampCtrlGroup);
|
||||
|
||||
ampGraph = new GraphWidget(this);
|
||||
ampGraph->configureAmperometricPlot();
|
||||
ampLayout->addWidget(ampGraph);
|
||||
|
||||
mainTabWidget->addTab(ampTab, "Amperometry");
|
||||
|
||||
// --- Tab 3: Voltammetry (LSV) ---
|
||||
QWidget *lsvTab = new QWidget();
|
||||
QVBoxLayout *lsvLayout = new QVBoxLayout(lsvTab);
|
||||
lsvLayout->setContentsMargins(4, 4, 4, 4);
|
||||
|
||||
QGroupBox *lsvCtrlGroup = new QGroupBox("LSV Controls", lsvTab);
|
||||
QHBoxLayout *lsvCtrlLayout = new QHBoxLayout(lsvCtrlGroup);
|
||||
|
||||
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");
|
||||
|
||||
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;");
|
||||
|
||||
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);
|
||||
|
||||
lsvLayout->addWidget(lsvCtrlGroup);
|
||||
|
||||
lsvGraph = new GraphWidget(this);
|
||||
lsvGraph->configureLSVPlot();
|
||||
lsvLayout->addWidget(lsvGraph);
|
||||
|
||||
mainTabWidget->addTab(lsvTab, "Voltammetry");
|
||||
|
||||
// ========================================================================
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
mainLayout->addWidget(splitter);
|
||||
|
||||
// ========================================================================
|
||||
// Initial State & Connections
|
||||
// ========================================================================
|
||||
checkIdBtn->setEnabled(false);
|
||||
calibrateBtn->setEnabled(false);
|
||||
checkShortRe0Se0->setEnabled(false);
|
||||
comboRangeLP->setEnabled(false);
|
||||
comboRangeHP->setEnabled(false);
|
||||
comboRLoad->setEnabled(false);
|
||||
|
||||
// Disable all mode controls initially
|
||||
impTab->setEnabled(false);
|
||||
ampTab->setEnabled(false);
|
||||
lsvTab->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);
|
||||
|
||||
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;");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onBlinkTimer() {
|
||||
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;");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,90 @@
|
|||
// File: aluf/src/main.cpp
|
||||
// host/src/main.cpp
|
||||
#include <QApplication>
|
||||
#include <QStyleFactory>
|
||||
#include <QPermissions>
|
||||
#include <QDebug>
|
||||
#include "MainWindow.h"
|
||||
|
||||
// Helper to request permissions sequentially
|
||||
void requestAndroidPermissions() {
|
||||
#ifdef Q_OS_ANDROID
|
||||
// Request Location Permission
|
||||
// Required for hardware discovery (BLE/WiFi) and sometimes USB device enumeration on Android.
|
||||
// Note: Storage permissions are handled via the Manifest and Scoped Storage;
|
||||
// explicit runtime requests for storage are often not needed for AppData folders.
|
||||
|
||||
qApp->requestPermission(QLocationPermission{}, [](const QPermission &permission) {
|
||||
if (permission.status() == Qt::PermissionStatus::Granted) {
|
||||
qDebug() << "Location Permission Granted";
|
||||
} else {
|
||||
qWarning() << "Location Permission Denied";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
// High DPI Scaling
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
|
||||
QApplication app(argc, argv);
|
||||
QApplication::setApplicationName("EIS Configurator");
|
||||
QApplication::setApplicationVersion("1.0");
|
||||
|
||||
// Dark Theme
|
||||
QPalette p = app.palette();
|
||||
p.setColor(QPalette::Window, QColor(30, 30, 30));
|
||||
// --- Apply Dark Fusion Theme ---
|
||||
app.setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
QPalette p;
|
||||
p.setColor(QPalette::Window, QColor(53, 53, 53));
|
||||
p.setColor(QPalette::WindowText, Qt::white);
|
||||
p.setColor(QPalette::Base, QColor(15, 15, 15));
|
||||
p.setColor(QPalette::AlternateBase, QColor(45, 45, 45));
|
||||
p.setColor(QPalette::Base, QColor(25, 25, 25));
|
||||
p.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
|
||||
p.setColor(QPalette::ToolTipBase, Qt::white);
|
||||
p.setColor(QPalette::ToolTipText, Qt::white);
|
||||
p.setColor(QPalette::Text, Qt::white);
|
||||
p.setColor(QPalette::Button, QColor(45, 45, 45));
|
||||
p.setColor(QPalette::Button, QColor(53, 53, 53));
|
||||
p.setColor(QPalette::ButtonText, Qt::white);
|
||||
p.setColor(QPalette::BrightText, Qt::red);
|
||||
p.setColor(QPalette::Link, QColor(42, 130, 218));
|
||||
p.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
||||
p.setColor(QPalette::HighlightedText, Qt::black);
|
||||
app.setPalette(p);
|
||||
|
||||
// --- Global Stylesheet for Modern Look ---
|
||||
app.setStyleSheet(
|
||||
"QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"
|
||||
"QGroupBox { border: 1px solid #555; border-radius: 5px; margin-top: 10px; font-weight: bold; }"
|
||||
"QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 3px; }"
|
||||
"QSpinBox, QDoubleSpinBox, QComboBox, QLineEdit { "
|
||||
" background: #333; color: #FFF; border: 1px solid #555; padding: 4px; border-radius: 4px; "
|
||||
" selection-background-color: #2A82DA; "
|
||||
"}"
|
||||
"QSpinBox::up-button, QDoubleSpinBox::up-button, QSpinBox::down-button, QDoubleSpinBox::down-button { "
|
||||
" background: #444; width: 16px; border-radius: 2px;"
|
||||
"}"
|
||||
"QPushButton { "
|
||||
" background-color: #444; border: 1px solid #555; border-radius: 4px; padding: 5px 15px; color: white; font-weight: bold;"
|
||||
"}"
|
||||
"QPushButton:hover { background-color: #555; }"
|
||||
"QPushButton:pressed { background-color: #2A82DA; }"
|
||||
"QPushButton:disabled { background-color: #333; color: #777; border: 1px solid #444; }"
|
||||
"QTabWidget::pane { border: 1px solid #444; }"
|
||||
"QTabBar::tab { background: #333; color: #AAA; padding: 8px 20px; border-top-left-radius: 4px; border-top-right-radius: 4px; }"
|
||||
"QTabBar::tab:selected { background: #535353; color: #FFF; border-bottom: 2px solid #2A82DA; }"
|
||||
"QScrollBar:vertical { background: #333; width: 12px; }"
|
||||
"QScrollBar::handle:vertical { background: #555; min-height: 20px; border-radius: 6px; }"
|
||||
"QLabel { color: #EEE; }"
|
||||
);
|
||||
|
||||
// Set default font size
|
||||
QFont font = app.font();
|
||||
font.setPointSize(12);
|
||||
app.setFont(font);
|
||||
|
||||
// Request Android Permissions at startup
|
||||
requestAndroidPermissions();
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
|
|
|
|||
425
main.c
425
main.c
|
|
@ -1,313 +1,79 @@
|
|||
// File: main.c
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/spi.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/watchdog.h"
|
||||
#include "ad5940.h"
|
||||
#include "Impedance.h"
|
||||
#include "App_Common.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
|
||||
|
||||
#define APPBUFF_SIZE 512
|
||||
// --- Global Variables ---
|
||||
uint32_t AppBuff[APPBUFF_SIZE];
|
||||
AppMode CurrentMode = MODE_IDLE;
|
||||
float LFOSCFreq = 32000.0;
|
||||
uint32_t g_AmpIndex = 0;
|
||||
uint32_t g_RampIndex = 0;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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; }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Application Logic
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void setup_pins(void) {
|
||||
spi_init(spi0, 4000000);
|
||||
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);
|
||||
}
|
||||
|
||||
void AD5940ImpedanceStructInit(void)
|
||||
{
|
||||
AppIMPCfg_Type *pImpedanceCfg;
|
||||
|
||||
AppIMPGetCfg(&pImpedanceCfg);
|
||||
|
||||
pImpedanceCfg->SeqStartAddr = 0;
|
||||
pImpedanceCfg->MaxSeqLen = 512;
|
||||
|
||||
pImpedanceCfg->RcalVal = 100.0;
|
||||
pImpedanceCfg->RtiaVal = 200.0;
|
||||
pImpedanceCfg->SinFreq = 1000.0;
|
||||
pImpedanceCfg->FifoThresh = 6;
|
||||
|
||||
pImpedanceCfg->DacVoltPP = 600.0;
|
||||
pImpedanceCfg->ExcitBufGain = EXCITBUFGAIN_0P25;
|
||||
pImpedanceCfg->HsDacGain = HSDACGAIN_0P2;
|
||||
|
||||
pImpedanceCfg->DswitchSel = SWD_CE0;
|
||||
pImpedanceCfg->PswitchSel = SWP_CE0;
|
||||
pImpedanceCfg->NswitchSel = SWN_SE0;
|
||||
pImpedanceCfg->TswitchSel = SWT_SE0LOAD;
|
||||
|
||||
pImpedanceCfg->HstiaRtiaSel = HSTIARTIA_200;
|
||||
pImpedanceCfg->BiasVolt = 0.0;
|
||||
|
||||
pImpedanceCfg->SweepCfg.SweepEn = bFALSE;
|
||||
pImpedanceCfg->SweepCfg.SweepStart = 100.0f;
|
||||
pImpedanceCfg->SweepCfg.SweepStop = 100000.0f;
|
||||
pImpedanceCfg->SweepCfg.SweepPoints = 50;
|
||||
pImpedanceCfg->SweepCfg.SweepLog = bTRUE;
|
||||
|
||||
pImpedanceCfg->PwrMod = AFEPWR_LP;
|
||||
pImpedanceCfg->ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
pImpedanceCfg->DftNum = DFTNUM_16384;
|
||||
pImpedanceCfg->DftSrc = DFTSRC_SINC3;
|
||||
}
|
||||
|
||||
static int32_t AD5940PlatformCfg(void)
|
||||
{
|
||||
CLKCfg_Type clk_cfg;
|
||||
FIFOCfg_Type fifo_cfg;
|
||||
AGPIOCfg_Type gpio_cfg;
|
||||
|
||||
AD5940_HWReset();
|
||||
AD5940_Initialize();
|
||||
|
||||
// Use HFOSC (16MHz) for stability across all frequencies
|
||||
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);
|
||||
printf("Clock Configured (HFOSC 16MHz).\n");
|
||||
|
||||
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, 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);
|
||||
|
||||
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// High-Level Routines
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Routine_CalibrateLFO(void) {
|
||||
printf(">> Calibrating LFOSC...\n");
|
||||
AppIMPCleanup();
|
||||
AppIMPCalibrateLFO();
|
||||
printf(">> LFOSC Calibrated.\n");
|
||||
}
|
||||
|
||||
void Routine_Measure(float freq) {
|
||||
AppIMPCfg_Type *pCfg;
|
||||
AppIMPGetCfg(&pCfg);
|
||||
|
||||
AppIMPCleanup();
|
||||
AppIMPCalibrateLFO();
|
||||
|
||||
pCfg->SweepCfg.SweepEn = bFALSE;
|
||||
pCfg->SinFreq = freq;
|
||||
pCfg->NumOfData = -1;
|
||||
pCfg->bParaChanged = bTRUE;
|
||||
|
||||
if(AppIMPInit(AppBuff, APPBUFF_SIZE) == AD5940ERR_OK) {
|
||||
AppIMPCtrl(IMPCTRL_START, 0);
|
||||
} else {
|
||||
printf("ERROR: Init Failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Routine_Sweep(float start, float end, int steps) {
|
||||
AppIMPCfg_Type *pCfg;
|
||||
AppIMPGetCfg(&pCfg);
|
||||
|
||||
AppIMPCleanup();
|
||||
AppIMPCalibrateLFO();
|
||||
|
||||
pCfg->SweepCfg.SweepEn = bTRUE;
|
||||
pCfg->SweepCfg.SweepStart = start;
|
||||
pCfg->SweepCfg.SweepStop = end;
|
||||
pCfg->SweepCfg.SweepPoints = steps;
|
||||
pCfg->SweepCfg.SweepLog = bTRUE;
|
||||
pCfg->NumOfData = steps;
|
||||
pCfg->bParaChanged = bTRUE;
|
||||
|
||||
if(AppIMPInit(AppBuff, APPBUFF_SIZE) == AD5940ERR_OK) {
|
||||
AppIMPCtrl(IMPCTRL_START, 0);
|
||||
} else {
|
||||
printf("ERROR: Init Failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
void Routine_CalibrateSystem(void) {
|
||||
AppIMPCfg_Type *pCfg;
|
||||
AppIMPGetCfg(&pCfg);
|
||||
|
||||
AppIMPCleanup();
|
||||
|
||||
// 1. ADC Calibration
|
||||
ADCPGACal_Type adcpga_cal;
|
||||
adcpga_cal.AdcClkFreq = 16000000.0;
|
||||
adcpga_cal.SysClkFreq = 16000000.0;
|
||||
adcpga_cal.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
adcpga_cal.ADCSinc2Osr = ADCSINC2OSR_22;
|
||||
adcpga_cal.ADCPga = ADCPGA_1P5;
|
||||
adcpga_cal.PGACalType = PGACALTYPE_OFFSET;
|
||||
adcpga_cal.TimeOut10us = 1000;
|
||||
adcpga_cal.VRef1p11 = 1.11;
|
||||
adcpga_cal.VRef1p82 = 1.82;
|
||||
printf(">> Calibrating ADC Offset...\n");
|
||||
AD5940_ADCPGACal(&adcpga_cal);
|
||||
|
||||
// 2. HSDAC Configuration
|
||||
HSDACCfg_Type hsdac_cfg;
|
||||
hsdac_cfg.ExcitBufGain = EXCITBUFGAIN_0P25;
|
||||
hsdac_cfg.HsDacGain = HSDACGAIN_0P2;
|
||||
hsdac_cfg.HsDacUpdateRate = 7;
|
||||
AD5940_HSDacCfgS(&hsdac_cfg);
|
||||
|
||||
// 3. RTIA Calibration
|
||||
HSRTIACal_Type hsrtia_cal;
|
||||
fImpPol_Type Res;
|
||||
memset(&hsrtia_cal, 0, sizeof(hsrtia_cal));
|
||||
|
||||
hsrtia_cal.fFreq = 1000.0f;
|
||||
hsrtia_cal.AdcClkFreq = 16000000.0;
|
||||
hsrtia_cal.SysClkFreq = 16000000.0;
|
||||
hsrtia_cal.ADCSinc3Osr = ADCSINC3OSR_4;
|
||||
hsrtia_cal.ADCSinc2Osr = ADCSINC2OSR_22;
|
||||
hsrtia_cal.bPolarResult = bTRUE;
|
||||
hsrtia_cal.fRcal = 100.0;
|
||||
hsrtia_cal.HsTiaCfg.DiodeClose = bFALSE;
|
||||
hsrtia_cal.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
|
||||
hsrtia_cal.HsTiaCfg.HstiaCtia = 31;
|
||||
hsrtia_cal.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_200;
|
||||
hsrtia_cal.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
|
||||
hsrtia_cal.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
|
||||
hsrtia_cal.DftCfg.DftNum = DFTNUM_16384;
|
||||
hsrtia_cal.DftCfg.DftSrc = DFTSRC_SINC3;
|
||||
|
||||
printf(">> Calibrating HSTIARTIA_200...\n");
|
||||
if (AD5940_HSRtiaCal(&hsrtia_cal, &Res) == AD5940ERR_OK) {
|
||||
printf("Calibrated Rtia: Mag = %f Ohm, Phase = %f\n", Res.Magnitude, Res.Phase);
|
||||
pCfg->RtiaVal = Res.Magnitude;
|
||||
} else {
|
||||
printf("Calibration Failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main Loop
|
||||
// ---------------------------------------------------------------------------
|
||||
uint32_t ConfigLptiaVal = 1000;
|
||||
uint32_t ConfigHstiaVal = 1000;
|
||||
uint32_t CurrentLpTiaRf = LPTIARF_20K;
|
||||
uint32_t ConfigRLoad = LPTIARLOAD_100R; // Default 100R
|
||||
float CalibratedLptiaVal = 1000.0f;
|
||||
float CalibratedHstiaVal = 1000.0f;
|
||||
BoolFlag GlobalShortRe0Se0 = bFALSE;
|
||||
|
||||
char input_buffer[64];
|
||||
int input_pos = 0;
|
||||
|
||||
void process_command() {
|
||||
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);
|
||||
}
|
||||
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 == '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 == 'c') {
|
||||
Routine_CalibrateLFO();
|
||||
Routine_CalibrateSystem();
|
||||
}
|
||||
else if (cmd == 'm') {
|
||||
|
|
@ -321,12 +87,27 @@ void process_command() {
|
|||
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') {
|
||||
AppIMPCleanup();
|
||||
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') {
|
||||
watchdog_reboot(0, 0, 0);
|
||||
SystemReset();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,8 +116,12 @@ int main() {
|
|||
sleep_ms(2000);
|
||||
|
||||
setup_pins();
|
||||
|
||||
AD5940_HWReset();
|
||||
AD5940_Initialize();
|
||||
AD5940PlatformCfg();
|
||||
AD5940ImpedanceStructInit();
|
||||
|
||||
Routine_CalibrateLFO();
|
||||
|
||||
printf("SYSTEM_READY\n");
|
||||
|
||||
|
|
@ -354,13 +139,49 @@ int main() {
|
|||
|
||||
if (gpio_get(PIN_INT) == 0) {
|
||||
uint32_t temp = APPBUFF_SIZE;
|
||||
int32_t status = AppIMPISR(AppBuff, &temp);
|
||||
int32_t status = 0;
|
||||
|
||||
if (status == AD5940ERR_FIFO) {
|
||||
printf("ERROR: FIFO Overflow/Underflow. Stopping.\n");
|
||||
AppIMPCleanup();
|
||||
} else if(temp > 0) {
|
||||
ImpedanceShowResult(AppBuff, temp);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue