This commit is contained in:
pszsh 2026-03-06 17:11:01 -08:00
parent 4d1cf3de48
commit 2d8ee9c616
61 changed files with 35668 additions and 8 deletions

811
#AD5940.h reference.md Normal file
View File

@ -0,0 +1,811 @@
# # AD594x AFE Controller: A Streamlined Register and Functional Reference
--------------------------------------------------------------------------------
### 1.0 Introduction
This document serves as a streamlined, developer-focused technical reference for the Analog Devices AD594x Analog Front End (AFE) controller. Its purpose is to provide a clear, structured, and condensed guide to the chip's register map and core functionalities, optimized for clarity and ease of use.
The reference is organized by peripheral module, starting with general-purpose blocks such as I/O and timers. It then proceeds to the central AFE Controller, the watchdog and wakeup timers, and other essential hardware accelerators. The core of the document provides a detailed breakdown of the Analog Front End (AFE) block itself, covering everything from the sequencer and waveform generator to the ADC, TIA, and switch matrix. The document concludes with a practical functional example demonstrating how these registers and features are used to perform a low-frequency oscillator calibration.
### 2.0 General Purpose I/O (AGPIO)
The General Purpose I/O (AGPIO) module is the primary interface for configuring and controlling the AD594x's digital input/output pins. This block allows each pin to be configured for various functions, with direct control over output state, pull-up/pull-down resistors, and input path enabling. This module is crucial for tasks like triggering external events, synchronizing with other ICs, or providing simple status indicators (e.g., LEDs).
**AGPIO_GP0CON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0CON** | 0x00000000 | 0x00000000 | AGPIO GPIO Port 0 Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 14-15 | **PIN7CFG** | 0x0000C000 | P0.7 Configuration Bits |
| 12-13 | **PIN6CFG** | 0x00003000 | P0.6 Configuration Bits |
| 10-11 | **PIN5CFG** | 0x00000C00 | P0.5 Configuration Bits |
| 8-9 | **PIN4CFG** | 0x00000300 | P0.4 Configuration Bits |
| 6-7 | **PIN3CFG** | 0x000000C0 | P0.3 Configuration Bits |
| 4-5 | **PIN2CFG** | 0x00000030 | P0.2 Configuration Bits |
| 2-3 | **PIN1CFG** | 0x0000000C | P0.1 Configuration Bits |
| 0-1 | **PIN0CFG** | 0x00000003 | P0.0 Configuration Bits |
**AGPIO_GP0OEN**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0OEN** | 0x00000004 | 0x00000000 | AGPIO GPIO Port 0 Output Enable |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **OEN** | 0x000000FF | Pin Output Drive Enable |
**AGPIO_GP0PE**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0PE** | 0x00000008 | 0x00000000 | AGPIO GPIO Port 0 Pull-up/Pull-down Enable |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **PE** | 0x000000FF | Pin Pull Enable |
**AGPIO_GP0IEN**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0IEN** | 0x0000000C | 0x00000000 | AGPIO GPIO Port 0 Input Path Enable |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **IEN** | 0x000000FF | Input Path Enable |
**AGPIO_GP0IN**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0IN** | 0x00000010 | 0x00000000 | AGPIO GPIO Port 0 Registered Data Input |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **IN** | 0x000000FF | Registered Data Input |
**AGPIO_GP0OUT**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0OUT** | 0x00000014 | 0x00000000 | AGPIO GPIO Port 0 Data Output |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **OUT** | 0x000000FF | Data Out |
**AGPIO_GP0SET**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0SET** | 0x00000018 | 0x00000000 | AGPIO GPIO Port 0 Data Out Set |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **SET** | 0x000000FF | Set the Output HIGH |
**AGPIO_GP0CLR**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0CLR** | 0x0000001C | 0x00000000 | AGPIO GPIO Port 0 Data Out Clear |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **CLR** | 0x000000FF | Set the Output LOW |
**AGPIO_GP0TGL**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **GP0TGL** | 0x00000020 | 0x00000000 | AGPIO GPIO Port 0 Pin Toggle |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **TGL** | 0x000000FF | Toggle the Output |
These registers provide a comprehensive interface for managing the digital I/O pins, transitioning next to the core AFE controller block.
### 3.0 AFE Controller (AFECON)
The AFE Controller (AFECON) module is the central control block for the entire AFE. It is responsible for fundamental operations such as chip identification, system and Analog-to-Digital Converter (ADC) clock configuration, software resets, and triggering measurement sequences.
**AFECON_ADIID**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **ADIID** | 0x00000400 | 0x00000000 | ADI Identification |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **ADIID** | 0x0000FFFF | ADI Identifier. |
**AFECON_CHIPID**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CHIPID** | 0x00000404 | 0x00000000 | Chip Identification |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 4-15 | **PARTID** | 0x0000FFF0 | Part Identifier |
| 0-3 | **REVISION** | 0x0000000F | Silicon Revision Number |
**AFECON_CLKCON0**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CLKCON0** | 0x00000408 | 0x00000441 | Clock Divider Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 10-15 | **SFFTCLKDIVCNT** | 0x0000FC00 | SFFT Clock Divider Configuration |
| 6-9 | **ADCCLKDIV** | 0x000003C0 | ADC Clock Divider Configuration |
| 0-5 | **SYSCLKDIV** | 0x0000003F | System Clock Divider Configuration |
**AFECON_CLKEN1**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CLKEN1** | 0x00000410 | 0x000002C0 | Clock Gate Enable |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 7 | **GPT1DIS** | 0x00000080 | GPT1 Clock Enable |
| 6 | **GPT0DIS** | 0x00000040 | GPT0 Clock Enable |
| 5 | **ACLKDIS** | 0x00000020 | ACLK Clock Enable |
**AFECON_CLKSEL**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CLKSEL** | 0x00000414 | 0x00000000 | Clock Select |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 2-3 | **ADCCLKSEL** | 0x0000000C | Select ADC Clock Source |
| 0-1 | **SYSCLKSEL** | 0x00000003 | Select System Clock Source |
**AFECON_CLKCON0KEY**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CLKCON0KEY** | 0x00000420 | 0x00000000 | Enable Clock Division to 8Mhz, 4Mhz and 2Mhz |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **DIVSYSCLK_ULP_EN** | 0x0000FFFF | Enable Clock Division to 8Mhz, 4Mhz and 2Mhz |
**AFECON_SWRSTCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **SWRSTCON** | 0x00000424 | 0x00000001 | Software Reset |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **SWRSTL** | 0x0000FFFF | Software Reset |
**AFECON_TRIGSEQ**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **TRIGSEQ** | 0x00000430 | 0x00000000 | Trigger Sequence |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 3 | **TRIG3** | 0x00000008 | Trigger Sequence 3 |
| 2 | **TRIG2** | 0x00000004 | Trigger Sequence 2 |
| 1 | **TRIG1** | 0x00000002 | Trigger Sequence 1 |
| 0 | **TRIG0** | 0x00000001 | Trigger Sequence 0 |
With the central controller defined, the next section covers the system's safety watchdog timer.
### 4.0 AFE Watchdog Timer (AFEWDT)
The AFE Watchdog Timer (AFEWDT) is a critical safety feature designed to monitor system operation. It can be configured to trigger a system reset or generate an interrupt if the application software fails to refresh it within a specified time period, preventing the system from becoming unresponsive.
**AFEWDT_WDTLD**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WDTLD** | 0x00000900 | Undefined | Watchdog Timer Load Value |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **LOAD** | 0x0000FFFF | WDT Load Value |
**AFEWDT_WDTVALS**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WDTVALS** | 0x00000904 | Undefined | Current Count Value |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **CCOUNT** | 0x0000FFFF | Current WDT Count Value. |
**AFEWDT_WDTCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WDTCON** | 0x00000908 | Undefined | Watchdog Timer Control Register |
| Bit Position(s) | Bitfield Name | Bitmask | Description | Settings/Enumerations |
|---|---|---|---|---|
| 10 | **WDTIRQEN** | 0x00000400 | WDT Interrupt Enable | |
| 9 | **MINLOAD_EN** | 0x00000200 | Timer Window Control | |
| 8 | **CLKDIV2** | 0x00000100 | Clock Source | |
| 6 | **MDE** | 0x00000040 | Timer Mode Select | |
| 5 | **EN** | 0x00000020 | Timer Enable | |
| 2-3 | **PRE** | 0x0000000C | Prescaler. | |
| 1 | **IRQ** | 0x00000002 | WDT Interrupt Enable | 0 **(RESET):** Watchdog timer timeout creates a reset. <br> 1 **(INTERRUPT):** Watchdog timer timeout creates an interrupt. |
| 0 | **PDSTOP** | 0x00000001 | Power Down Stop Enable | 0 **(CONTINUE):** Continue counting in Hibernate mode. <br> 1 **(STOP):** Stop counter in Hibernate mode. |
**AFEWDT_WDTCLRI**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WDTCLRI** | 0x0000090C | Undefined | Refresh Watchdog Register |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **CLRWDG** | 0x0000FFFF | Refresh Register |
**AFEWDT_WDTSTA**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WDTSTA** | 0x00000918 | Undefined | Timer Status |
| Bit Position(s) | Bitfield Name | Bitmask | Description | Settings/Enumerations |
|---|---|---|---|---|
| 6 | **TMINLD** | 0x00000040 | WDTMINLD Write Status | |
| 5 | **OTPWRDONE** | 0x00000020 | Reset Type Status | |
| 4 | **LOCK** | 0x00000010 | Lock Status | 0 **(OPEN):** Timer operation is not locked. <br> 1 **(LOCKED):** Timer is enabled and locked. |
| 3 | **CON** | 0x00000008 | WDTCON Write Status | |
| 2 | **TLD** | 0x00000004 | WDTVAL Write Status | 0 **(SYNC_COMPLETE):** WDTLD values match across clock domains. <br> 1 **(SYNC_IN_PROGRESS):** Synchronize in progress. |
| 1 | **CLRI** | 0x00000002 | WDTCLRI Write Status | |
| 0 | **IRQ** | 0x00000001 | WDT Interrupt | 0 **(CLEARED):** Watchdog timer interrupt is not pending. <br> 1 **(PENDING):** Watchdog timer interrupt is pending. |
**AFEWDT_WDTMINLD**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WDTMINLD** | 0x0000091C | Undefined | Minimum Load Value |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **MIN_LOAD** | 0x0000FFFF | WDT Min Load Value |
Complementing the watchdog is the low-power wakeup timer, which enables periodic operation.
### 5.0 Wakeup Timer (WUPTMR)
The Wakeup Timer (WUPTMR) is a strategic peripheral for managing low-power sleep and wake cycles. It enables the AD594x to enter a low-power hibernate state and wake up at precise, user-defined intervals to execute measurement sequences. This functionality is crucial for applications requiring periodic measurements with high precision while minimizing power consumption.
**WUPTMR_CON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CON** | 0x00000800 | 0x00000000 | Timer Control |
| Bit Position(s) | Bitfield Name | Bitmask | Description | Settings/Enumerations |
|---|---|---|---|---|
| 6 | **MSKTRG** | 0x00000040 | Mark Sequence Trigger from Sleep Wakeup Timer | |
| 4-5 | **CLKSEL** | 0x00000030 | Clock Selection | 0x0 **(SWT32K0):** Internal 32kHz OSC<br>0x1 **(SWTEXT0):** External Clock<br>0x2 **(SWT32K):** Internal 32kHz OSC<br>0x3 **(SWTEXT):** External Clock |
| 1-3 | **ENDSEQ** | 0x0000000E | End Sequence | 0x0 **(ENDSEQA):** Stop at SeqA, loop to SeqA<br>0x1 **(ENDSEQB):** Stop at SeqB, loop to SeqA<br>0x2 **(ENDSEQC):** Stop at SeqC, loop to SeqA<br>0x3 **(ENDSEQD):** Stop at SeqD, loop to SeqA<br>0x4 **(ENDSEQE):** Stop at SeqE, loop to SeqA<br>0x5 **(ENDSEQF):** Stop at SeqF, loop to SeqA<br>0x6 **(ENDSEQG):** Stop at SeqG, loop to SeqA<br>0x7 **(ENDSEQH):** Stop at SeqH, loop to SeqA |
| 0 | **EN** | 0x00000001 | Sleep Wake Timer Enable Bit | 0 **(SWTEN):** Enable Sleep Wakeup Timer<br>1 **(SWTDIS):** Disable Sleep Wakeup Timer |
**WUPTMR_SEQORDER**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **SEQORDER** | 0x00000804 | 0x00000000 | Order Control |
| Bit Position(s) | Bitfield Name | Bitmask | Description | Settings/Enumerations |
|---|---|---|---|---|
| 14-15 | **SEQH** | 0x0000C000 | SEQH Config | 0x0 **(SEQH0):** Fill SEQ0 In<br>0x1 **(SEQH1):** Fill SEQ1 In<br>0x2 **(SEQH2):** Fill SEQ2 In<br>0x3 **(SEQH3):** Fill SEQ3 In |
| 12-13 | **SEQG** | 0x00003000 | SEQG Config | 0x0 **(SEQG0):** Fill SEQ0 In<br>0x1 **(SEQG1):** Fill SEQ1 In<br>0x2 **(SEQG2):** Fill SEQ2 In<br>0x3 **(SEQG3):** Fill SEQ3 In |
| 10-11 | **SEQF** | 0x00000C00 | SEQF Config | 0x0 **(SEQF0):** Fill SEQ0 In<br>0x1 **(SEQF1):** Fill SEQ1 In<br>0x2 **(SEQF2):** Fill SEQ2 In<br>0x3 **(SEQF3):** Fill SEQ3 In |
| 8-9 | **SEQE** | 0x00000300 | SEQE Config | 0x0 **(SEQE0):** Fill SEQ0 In<br>0x1 **(SEQE1):** Fill SEQ1 In<br>0x2 **(SEQE2):** Fill SEQ2 In<br>0x3 **(SEQE3):** Fill SEQ3 In |
| 6-7 | **SEQD** | 0x000000C0 | SEQD Config | 0x0 **(SEQD0):** Fill SEQ0 In<br>0x1 **(SEQD1):** Fill SEQ1 In<br>0x2 **(SEQD2):** Fill SEQ2 In<br>0x3 **(SEQD3):** Fill SEQ3 In |
| 4-5 | **SEQC** | 0x00000030 | SEQC Config | 0x0 **(SEQC0):** Fill SEQ0 In<br>0x1 **(SEQC1):** Fill SEQ1 In<br>0x2 **(SEQC2):** Fill SEQ2 In<br>0x3 **(SEQC3):** Fill SEQ3 In |
| 2-3 | **SEQB** | 0x0000000C | SEQB Config | 0x0 **(SEQB0):** Fill SEQ0 In<br>0x1 **(SEQB1):** Fill SEQ1 In<br>0x2 **(SEQB2):** Fill SEQ2 In<br>0x3 **(SEQB3):** Fill SEQ3 In |
| 0-1 | **SEQA** | 0x00000003 | SEQA Config | 0x0 **(SEQA0):** Fill SEQ0 In<br>0x1 **(SEQA1):** Fill SEQ1 In<br>0x2 **(SEQA2):** Fill SEQ2 In<br>0x3 **(SEQA3):** Fill SEQ3 In |
**WUPTMR_SEQxWUP / SEQxSLEEP Registers**
The timer provides four sets of registers for defining sleep and active periods for different sequences. These register pairs define the 20-bit sleep and active (wakeup) periods for each of the four sequences (0-3).
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **SEQ0WUPL** | 0x00000808 | 0x0000FFFF | SEQ0 Wakeup Time (LSB) |
| **SEQ0WUPH** | 0x0000080C | 0x0000000F | SEQ0 Wakeup Time (MSB) |
| **SEQ0SLEEPL** | 0x00000810 | 0x0000FFFF | SEQ0 Sleep Time (LSB) |
| **SEQ0SLEEPH** | 0x00000814 | 0x0000000F | SEQ0 Sleep Time (MSB) |
| **SEQ1WUPL** | 0x00000818 | 0x0000FFFF | SEQ1 Wakeup Time (LSB) |
| **SEQ1WUPH** | 0x0000081C | 0x0000000F | SEQ1 Wakeup Time (MSB) |
| **SEQ1SLEEPL** | 0x00000820 | 0x0000FFFF | SEQ1 Sleep Time (LSB) |
| **SEQ1SLEEPH** | 0x00000824 | 0x0000000F | SEQ1 Sleep Time (MSB) |
| **SEQ2WUPL** | 0x00000828 | 0x0000FFFF | SEQ2 Wakeup Time (LSB) |
| **SEQ2WUPH** | 0x0000082C | 0x0000000F | SEQ2 Wakeup Time (MSB) |
| **SEQ2SLEEPL** | 0x00000830 | 0x0000FFFF | SEQ2 Sleep Time (LSB) |
| **SEQ2SLEEPH** | 0x00000834 | 0x0000000F | SEQ2 Sleep Time (MSB) |
| **SEQ3WUPL** | 0x00000838 | 0x0000FFFF | SEQ3 Wakeup Time (LSB) |
| **SEQ3WUPH** | 0x0000083C | 0x0000000F | SEQ3 Wakeup Time (MSB) |
| **SEQ3SLEEPL** | 0x00000840 | 0x0000FFFF | SEQ3 Sleep Time (LSB) |
| **SEQ3SLEEPH** | 0x00000844 | 0x0000000F | SEQ3 Sleep Time (MSB) |
The wakeup timer relies on the fundamental clock sources and power modes managed by the Always-On register block.
### 6.0 Always-On Registers (ALLON)
The Always-On (ALLON) register block controls the most fundamental aspects of the chip's operation. These registers manage power modes, core oscillators, and reset status, and their settings persist even when the chip is in its lowest power states.
**ALLON_PWRMOD**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **PWRMOD** | 0x00000A00 | 0x00000001 | Power Modes |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 15 | **RAMRETEN** | 0x00008000 | Retention for RAM |
| 14 | **ADCRETEN** | 0x00004000 | Keep ADC Power Switch on in Hibernate |
| 3 | **SEQSLPEN** | 0x00000008 | Auto Sleep by Sequencer Command |
| 2 | **TMRSLPEN** | 0x00000004 | Auto Sleep by Sleep Wakeup Timer |
| 0-1 | **PWRMOD** | 0x00000003 | Power Mode Control Bits |
**ALLON_OSCCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **OSCCON** | 0x00000A10 | 0x00000003 | Oscillator Control |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 10 | **HFXTALOK** | 0x00000400 | Status of HFXTAL Oscillator |
| 9 | **HFOSCOK** | 0x00000200 | Status of HFOSC Oscillator |
| 8 | **LFOSCOK** | 0x00000100 | Status of LFOSC Oscillator |
| 2 | **HFXTALEN** | 0x00000004 | High Frequency Crystal Oscillator Enable |
| 1 | **HFOSCEN** | 0x00000002 | High Frequency Internal Oscillator Enable |
| 0 | **LFOSCEN** | 0x00000001 | Low Frequency Internal Oscillator Enable |
**ALLON_RSTSTA**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **RSTSTA** | 0x00000A40 | 0x00000000 | Reset Status |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 4 | **PINSWRST** | 0x00000010 | Software Reset Pin |
| 3 | **MMRSWRST** | 0x00000008 | MMR Software Reset |
| 2 | **WDRST** | 0x00000004 | Watchdog Timeout |
| 1 | **EXTRST** | 0x00000002 | External Reset |
| 0 | **POR** | 0x00000001 | Power-on Reset |
**ALLON_CLKEN0**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CLKEN0** | 0x00000A70 | 0x00000004 | 32KHz Peripheral Clock Enable |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 2 | **TIACHPDIS** | 0x00000004 | TIA Chop Clock Disable |
| 1 | **SLPWUTDIS** | 0x00000002 | Sleep/Wakeup Timer Clock Disable |
| 0 | **WDTDIS** | 0x00000001 | Watch Dog Timer Clock Disable |
These core settings provide the foundation for higher-level peripherals, such as the general-purpose timers.
### 7.0 General Purpose Timers (AGPT0 & AGPT1)
The AD594x includes two 16-bit general-purpose timers, AGPT0 and AGPT1. These timers are highly configurable and can be used for a variety of tasks, including event timing, generating interrupts, signal capture, and Pulse Width Modulation (PWM) generation.
**7.1 Timer 0 (AGPT0)**
**AGPT0_LD0**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **LD0** | 0x00000D00 | Undefined | 16-bit Load Value Register. |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **LOAD** | 0x0000FFFF | Load Value |
**AGPT0_VAL0**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **VAL0** | 0x00000D04 | Undefined | 16-Bit Timer Value Register. |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **VAL** | 0x0000FFFF | Current Count |
**AGPT0_CON0**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CON0** | 0x00000D08 | Undefined | Control Register. |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 15 | **SYNCBYP** | 0x00008000 | Synchronization Bypass |
| 14 | **RSTEN** | 0x00004000 | Counter and Prescale Reset Enable |
| 13 | **EVTEN** | 0x00002000 | Event Select |
| 8-12 | **EVENT** | 0x00001F00 | Event Select Range |
| 7 | **RLD** | 0x00000080 | Reload Control |
| 5-6 | **CLK** | 0x00000060 | Clock Select |
| 4 | **ENABLE** | 0x00000010 | Timer Enable |
| 3 | **MOD** | 0x00000008 | Timer Mode |
| 2 | **UP** | 0x00000004 | Count up |
| 0-1 | **PRE** | 0x00000003 | Prescaler |
**AGPT0_PWMCON0**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **PWMCON0** | 0x00000D20 | Undefined | PWM Control Register. |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 1 | **IDLE** | 0x00000002 | PWM Idle State |
| 0 | **MATCHEN** | 0x00000001 | PWM Match Enabled |
**AGPT0_PWMMAT0**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **PWMMAT0** | 0x00000D24 | Undefined | PWM Match Value Register. |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **MATCHVAL** | 0x0000FFFF | PWM Match Value |
**7.2 Timer 1 (AGPT1)**
**AGPT1_LD1**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **LD1** | 0x00000E00 | Undefined | 16-bit Load Value Register |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **LOAD** | 0x0000FFFF | Load Value |
**AGPT1_VAL1**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **VAL1** | 0x00000E04 | Undefined | 16-bit Timer Value Register |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **VAL** | 0x0000FFFF | Current Count |
**AGPT1_CON1**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CON1** | 0x00000E08 | Undefined | Control Register |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 15 | **SYNCBYP** | 0x00008000 | Synchronization Bypass |
| 14 | **RSTEN** | 0x00004000 | Counter and Prescale Reset Enable |
| 13 | **EVENTEN** | 0x00002000 | Event Select |
| 8-12 | **EVENT** | 0x00001F00 | Event Select Range |
| 7 | **RLD** | 0x00000080 | Reload Control |
| 5-6 | **CLK** | 0x00000060 | Clock Select |
| 4 | **ENABLE** | 0x00000010 | Timer Enable |
| 3 | **MOD** | 0x00000008 | Timer Mode |
| 2 | **UP** | 0x00000004 | Count up |
| 0-1 | **PRE** | 0x00000003 | Prescaler |
**AGPT1_PWMCON1**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **PWMCON1** | 0x00000E20 | Undefined | PWM Control Register |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 1 | **IDLE** | 0x00000002 | PWM Idle State. |
| 0 | **MATCHEN** | 0x00000001 | PWM Match Enabled. |
**AGPT1_PWMMAT1**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **PWMMAT1** | 0x00000E24 | Undefined | PWM Match Value Register |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **MATCHVAL** | 0x0000FFFF | PWM Match Value |
For data integrity tasks, the device includes a dedicated hardware accelerator.
## 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 |
|---|---|---|---|
| **CTL** | 0x00001000 | Undefined | CRC Control Register |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 4 | **W16SWP** | 0x00000010 | Word16 Swap Enabled. |
| 3 | **BYTMIRR** | 0x00000008 | Byte Mirroring. |
| 2 | **BITMIRR** | 0x00000004 | Bit Mirroring. |
| 1 | **LSBFIRST** | 0x00000002 | LSB First Calculation Order |
| 0 | **EN** | 0x00000001 | CRC Peripheral Enable |
**AFECRC_IPDATA**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **IPDATA** | 0x00001004 | Undefined | Data Input. |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-31 | **VALUE** | 0xFFFFFFFF | Data Input. |
**AFECRC_RESULT**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **RESULT** | 0x00001008 | Undefined | CRC Residue |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-31 | **VALUE** | 0xFFFFFFFF | CRC Residue |
**AFECRC_POLY**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **POLY** | 0x0000100C | Undefined | CRC Reduction Polynomial |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-31 | **VALUE** | 0xFFFFFFFF | CRC Reduction Polynomial |
**AFECRC_IPBITS**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **IPBITS** | 0x00001010 | Undefined | Input Data Bits |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **DATA_BITS** | 0x000000FF | Input Data Bits. |
**AFECRC_IPBYTE**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **IPBYTE** | 0x00001014 | Undefined | Input Data Byte |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **DATA_BYTE** | 0x000000FF | Input Data Byte. |
**AFECRC_CRC_SIG_COMP**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CRC_SIG_COMP** | 0x00001020 | Undefined | CRC Signature Compare Data Input |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-31 | **CRC_SIG** | 0xFFFFFFFF | CRC Signature Compare Data Input. |
**AFECRC_CRCINTEN**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **CRCINTEN** | 0x00001024 | Undefined | CRC Error Interrupt Enable |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0 | **CRC_ERR_EN** | 0x00000001 | CRC Error Interrupt Enable Bit |
**AFECRC_INTSTA**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **INTSTA** | 0x00001028 | Undefined | CRC Error Interrupt Status |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0 | **CRC_ERR_ST** | 0x00000001 | CRC Error Interrupt Status Bit |
The next section details the main Analog Front End, which integrates all measurement capabilities.
### 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.
**10.1 Core and Sequencer Configuration**
**AFE_AFECON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **AFECON** | 0x00002000 | 0x00080000 | AFE Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 21 | **DACBUFEN** | 0x00200000 | Enable DC DAC Buffer |
| 20 | **DACREFEN** | 0x00100000 | High Speed DAC Reference Enable |
| 19 | **ALDOILIMITEN** | 0x00080000 | Analog LDO Current Limiting Enable |
| 16 | **SINC2EN** | 0x00010000 | ADC Output 50/60Hz Filter Enable |
| 15 | **DFTEN** | 0x00008000 | DFT Hardware Accelerator Enable |
| 14 | **WAVEGENEN** | 0x00004000 | Waveform Generator Enable |
| 13 | **TEMPCONVEN** | 0x00002000 | ADC Temp Sensor Convert Enable |
| 12 | **TEMPSENSEN** | 0x00001000 | ADC Temperature Sensor Channel Enable |
| 11 | **TIAEN** | 0x00000800 | High Power TIA Enable |
| 10 | **INAMPEN** | 0x00000400 | Enable Excitation Amplifier |
| 9 | **EXBUFEN** | 0x00000200 | Enable Excitation Buffer |
| 8 | **ADCCONVEN** | 0x00000100 | ADC Conversion Start Enable |
| 7 | **ADCEN** | 0x00000080 | ADC Power Enable |
| 6 | **DACEN** | 0x00000040 | High Power DAC Enable |
| 5 | **HPREFDIS** | 0x00000020 | Disable High Power Reference |
**AFE_SEQCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **SEQCON** | 0x00002004 | 0x00000002 | Sequencer Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 8-15 | **SEQWRTMR** | 0x0000FF00 | Timer for Sequencer Write Commands |
| 4 | **SEQHALT** | 0x00000010 | Halt Seq |
| 1 | **SEQHALTFIFOEMPTY** | 0x00000002 | Halt Sequencer If Empty |
| 0 | **SEQEN** | 0x00000001 | Enable Sequencer |
**AFE_FIFOCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **FIFOCON** | 0x00002008 | 0x00001010 | FIFOs Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 13-15 | **DATAFIFOSRCSEL** | 0x0000E000 | Selects the Source for the Data FIFO. |
| 11 | **DATAFIFOEN** | 0x00000800 | Data FIFO Enable. |
**AFE_SEQCRC**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **SEQCRC** | 0x00002060 | 0x00000001 | Sequencer CRC Value |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-7 | **CRC** | 0x000000FF | Sequencer Command CRC Value. |
**AFE_SEQCNT**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **SEQCNT** | 0x00002064 | 0x00000000 | Sequencer Command Count |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-15 | **COUNT** | 0x0000FFFF | Sequencer Command Count |
**AFE_SEQTIMEOUT**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **SEQTIMEOUT** | 0x00002068 | 0x00000000 | Sequencer Timeout Counter |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-29 | **TIMEOUT** | 0x3FFFFFFF | Current Value of the Sequencer Timeout Counter. |
**10.2 Waveform Generator**
**AFE_WGCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WGCON** | 0x00002014 | 0x00000030 | Waveform Generator Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 5 | **DACGAINCAL** | 0x00000020 | Bypass DAC Gain |
| 4 | **DACOFFSETCAL** | 0x00000010 | Bypass DAC Offset |
| 1-2 | **TYPESEL** | 0x00000006 | Selects the Type of Waveform |
| 0 | **TRAPRSTEN** | 0x00000001 | Resets the Trapezoid Waveform Generator |
**AFE_WGFCW**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WGFCW** | 0x00002030 | 0x00000000 | Waveform Generator - Sinusoid Frequency Control Word |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-23 | **SINEFCW** | 0x00FFFFFF | Sinusoid Generator Frequency Control Word |
**AFE_WGPHASE**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WGPHASE** | 0x00002034 | 0x00000000 | Waveform Generator - Sinusoid Phase Offset |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-19 | **SINEOFFSET** | 0x000FFFFF | Sinusoid Phase Offset |
**AFE_WGOFFSET**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WGOFFSET** | 0x00002038 | 0x00000000 | Waveform Generator - Sinusoid Offset |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-11 | **SINEOFFSET** | 0x00000FFF | Sinusoid Offset |
**AFE_WGAMPLITUDE**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WGAMPLITUDE** | 0x0000203C | 0x00000000 | Waveform Generator - Sinusoid Amplitude |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 0-10 | **SINEAMPLITUDE** | 0x000007FF | Sinusoid Amplitude |
**Trapezoid Waveform Registers**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **WGDCLEVEL1** | 0x00002018 | 0x00000000 | Waveform Generator - Trapezoid DC Level 1 |
| **WGDCLEVEL2** | 0x0000201C | 0x00000000 | Waveform Generator - Trapezoid DC Level 2 |
| **WGDELAY1** | 0x00002020 | 0x00000000 | Waveform Generator - Trapezoid Delay 1 Time |
| **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 |
**10.3 ADC, Filters, and Data Path**
**AFE_ADCFILTERCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **ADCFILTERCON** | 0x00002044 | 0x00000301 | ADC Output Filters Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 14-15 | **AVRGNUM** | 0x0000C000 | Number of Samples Averaged |
| 12-13 | **SINC3OSR** | 0x00003000 | SINC3 OSR |
| 8-11 | **SINC2OSR** | 0x00000F00 | SINC2 OSR |
| 7 | **AVRGEN** | 0x00000080 | Average Function Enable |
| 6 | **SINC3BYP** | 0x00000040 | SINC3 Filter Bypass |
| 0 | **ADCCLK** | 0x00000001 | ADC Data Rate |
**AFE_ADCCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **ADCCON** | 0x000021A8 | 0x00000000 | ADC Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 16-18 | **GNPGA** | 0x00070000 | PGA Gain Setup |
| 15 | **GNOFSELPGA** | 0x00008000 | Internal Offset/Gain Cancellation |
| 8-12 | **MUXSELN** | 0x00001F00 | Select Negative Input |
| 0-5 | **MUXSELP** | 0x0000003F | Select Positive Input |
**AFE_DFTCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **DFTCON** | 0x000020D0 | 0x00000090 | AFE DSP Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 20-21 | **DFTINSEL** | 0x00300000 | DFT Input Select |
| 4-7 | **DFTNUM** | 0x000000F0 | ADC Samples Used |
| 0 | **HANNINGEN** | 0x00000001 | Hanning Window Enable |
**Data and FIFO Registers**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **ADCDAT** | 0x00002074 | 0x00000000 | ADC Raw Result |
| **DFTREAL** | 0x00002078 | 0x00000000 | DFT Result, Real Part |
| **DFTIMAG** | 0x0000207C | 0x00000000 | DFT Result, Imaginary Part |
| **SINC2DAT** | 0x00002080 | 0x00000000 | Supply Rejection Filter Result |
| **TEMPSENSDAT** | 0x00002084 | 0x00000000 | Temperature Sensor Result |
| **DATAFIFORD** | 0x0000206C | 0x00000000 | Data FIFO Read |
| **CMDFIFOWRITE** | 0x00002070 | 0x00000000 | Command FIFO Write |
**10.4 TIA and DAC Configuration**
**High-Speed DAC and TIA**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **HSDACCON** | 0x00002010 | 0x0000001E | High Speed DAC Configuration |
| **HSDACDAT** | 0x00002048 | 0x00000800 | HS DAC Code |
| **HSRTIACON** | 0x000020F0 | 0x0000000F | High Power RTIA Configuration |
| **DE0RESCON** | 0x000020F8 | 0x000000FF | DE0 HSTIA Resistors Configuration |
| **DE1RESCON** | 0x000020F4 | Undefined | DE1 HSTIA Resistors Configuration |
| **HSTIACON** | 0x000020FC | 0x00000000 | HSTIA Amplifier Configuration |
**Low-Power TIA and DAC**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **LPTIACON0** | 0x000020EC | 0x00000003 | ULPTIA Control Bits Channel 0 |
| **LPTIACON1** | 0x000020E8 | Undefined | ULPTIA Control Bits Channel 1 |
| **LPTIASW0** | 0x000020E4 | 0x00000000 | ULPTIA Switch Configuration for Channel 0 |
| **LPTIASW1** | 0x000020E0 | Undefined | ULPTIA Switch Configuration for Channel 1 |
| **LPDACDAT0** | 0x00002120 | 0x00000000 | LPDAC Data-out |
| **LPDACCON0** | 0x00002128 | 0x00000002 | LPDAC Control Bits |
| **LPDACDAT1** | 0x0000212C | Undefined | Low Power DAC1 data register |
| **LPDACCON1** | 0x00002134 | Undefined | ULP_DACCON1 |
**LPTIACON0/LPTIACON1 Bitfield Breakdown**
| Bit Position(s) | Bitfield Name | Bitmask | Description | Settings/Enumerations |
|---|---|---|---|---|
| 5-10 | **TIAGAIN** | 0x000003E0 | Set RTIA Gain Resistor | 0x00000000 (DISCONTIA): Disconnect<br>0x00000020 (TIAGAIN200): 200 Ω<br>0x00000040 (TIAGAIN1K): 1 kΩ<br>0x00000060 (TIAGAIN2K): 2 kΩ<br>0x00000080 (TIAGAIN3K): 3 kΩ<br>0x000000A0 (TIAGAIN4K): 4 kΩ<br>0x000000C0 (TIAGAIN6K): 6 kΩ<br>0x000000E0 (TIAGAIN8K): 8 kΩ<br>0x00000100 (TIAGAIN10K): 10 kΩ<br>0x00000120 (TIAGAIN12K): 12 kΩ<br>0x00000140 (TIAGAIN16K): 16 kΩ<br>0x00000160 (TIAGAIN20K): 20 kΩ<br>0x00000180 (TIAGAIN24K): 24 kΩ<br>0x000001A0 (TIAGAIN30K): 30 kΩ<br>0x000001C0 (TIAGAIN32K): 32 kΩ<br>0x000001E0 (TIAGAIN40K): 40 kΩ<br>0x00000200 (TIAGAIN48K): 48 kΩ<br>0x00000220 (TIAGAIN64K): 64 kΩ<br>0x00000240 (TIAGAIN85K): 85 kΩ<br>0x00000260 (TIAGAIN96K): 96 kΩ<br>0x00000280 (TIAGAIN100K): 100 kΩ<br>0x000002A0 (TIAGAIN120K): 120 kΩ<br>0x000002C0 (TIAGAIN128K): 128 kΩ<br>0x000002E0 (TIAGAIN160K): 160 kΩ<br>0x00000300 (TIAGAIN196K): 196 kΩ<br>0x00000320 (TIAGAIN256K): 256 kΩ<br>0x00000340 (TIAGAIN512K): 512 kΩ |
| 10-12 | **TIARL** | 0x00001C00 | Set RLOAD | 0x00000000 (RL0): 0 Ω<br>0x00000400 (RL10): 10 Ω<br>0x00000800 (RL30): 30 Ω<br>0x00000C00 (RL50): 50 Ω<br>0x00001000 (RL100): 100 Ω<br>0x00001400 (RL1P6K): 1.6 kΩ<br>0x00001800 (RL3P1K): 3.1 kΩ<br>0x00001C00 (RL3P5K): 3.6 kΩ |
| 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 | |
**10.5 Switch Matrix**
**AFE_SWCON**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **SWCON** | 0x0000200C | 0x0000FFFF | Switch Matrix Configuration |
| Bit Position(s) | Bitfield Name | Bitmask | Description |
|---|---|---|---|
| 16 | **SWSOURCESEL** | 0x00010000 | Switch Control Select |
| 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 |
**Switch Matrix Full Configuration and Status**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **DSWFULLCON** | 0x00002150 | 0x00000000 | Switch Matrix Full Configuration (D) |
| **NSWFULLCON** | 0x00002154 | 0x00000000 | Switch Matrix Full Configuration (N) |
| **PSWFULLCON** | 0x00002158 | 0x00000000 | Switch Matrix Full Configuration (P) |
| **TSWFULLCON** | 0x0000215C | 0x00000000 | Switch Matrix Full Configuration (T) |
| **DSWSTA** | 0x000021B0 | 0x00000000 | Switch Matrix Status (D) |
| **PSWSTA** | 0x000021B4 | 0x00006000 | Switch Matrix Status (P) |
| **NSWSTA** | 0x000021B8 | 0x00000C00 | Switch Matrix Status (N) |
| **TSWSTA** | 0x000021BC | 0x00000000 | Switch Matrix Status (T) |
**10.6 Power Management and Calibration**
| Register Name | Address | Reset Value | Description |
|---|---|---|---|
| **PMBW** | 0x000022F0 | 0x00088800 | Power Mode Configuration |
| **LPMODECON** | 0x00002114 | 0x00000102 | LPMODECON |
| **LPREFBUFCON** | 0x00002050 | 0x00000000 | LPREF_BUF_CON |
| **BUFSENCON** | 0x00002180 | 0x00000037 | HP and LP Buffer Control |
| **ADCOFFSETHSTIA** | 0x00002234 | 0x00000000 | ADC Offset Calibration High Speed TIA Channel |
| **ADCGNHSTIA** | 0x00002284 | 0x00004000 | ADC Gain Calibration for HS TIA Channel |
| **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.
### 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).
**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.
**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).
**3** **Write Sequences to SRAM:** Both sequences are loaded into the AFE's onboard SRAM.
**4** **Configure the Wakeup Timer:** The wakeup timer is configured to execute Sequence A first, then Sequence B, with a specific time duration (CalDuration) between them.
**5** **Run First Measurement:** The wakeup timer is enabled. It triggers Sequence A, which starts the timeout counter. After the specified CalDuration (e.g., 1000 ms), the timer triggers Sequence B. The END_SEQ interrupt from Sequence B prompts the host MCU to read the final count from the **SEQTIMEOUT** register. This value (TimerCount) represents the wakeup period plus the time required for the MCU to read the register.
**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.
**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)
Where:
* **Frequency**: The calculated Low Frequency Oscillator frequency in Hz.
* **CalDuration_in_ticks**: The length of the calibration period, specified in LFOSC clock ticks.
* **SystemClkFreq**: The frequency of the AD5940 system clock in Hz (ideally 16 MHz from an external crystal for highest accuracy).
* **TimerCount**: The value read from the **SEQTIMEOUT** register after the first measurement.
* **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.
**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.

View File

@ -1,6 +1,4 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(AutoSpa)
project(ad5940_webserver)

View File

@ -0,0 +1,4 @@
idf_component_register(SRCS "ad5940.c"
INCLUDE_DIRS ".")
target_compile_options(${COMPONENT_LIB} PRIVATE -w)

4422
components/ad5940/ad5940.c Normal file

File diff suppressed because it is too large Load Diff

6175
components/ad5940/ad5940.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
// File: AD5940_Platform.c
#include "App_Common.h"
#include "rp2040port.h"
// --- Platform Interface Implementation ---
// NOTE: Low-level hardware interface functions are now in rp2040port.c
int32_t AD5940PlatformCfg(void) {
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
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 100:
return HSTIARTIA_200; // Map 100 request to 200 Internal
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 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]);
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -0,0 +1,87 @@
// File: App_Common.h
#ifndef _APP_COMMON_H_
#define _APP_COMMON_H_
#include "Amperometric.h"
#include "Impedance.h"
#include "RampTest.h"
#include "ad5940.h"
#include "pico/stdlib.h"
#include "rp2040port.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// --- Hardware Definitions ---
#define PIN_MISO 0
#define PIN_CS 1
#define PIN_SCK 2
#define PIN_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 float CalibratedHstiaPhase; // Added for single-point phase correction
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, float bias_mv);
void Routine_Sweep(float start, float end, int steps, float bias_mv);
void Routine_Amperometric(float bias_mv);
void Routine_LSV(float start_mv, float end_mv, int steps, int duration_ms);
void Routine_CalibrateSystem(void);
#endif

View File

@ -0,0 +1,45 @@
# File: CMakeLists.txt
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(EIS C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
# Changed main.cpp to main.c
add_executable(EIS
main.c
ad5940.c
Impedance.c
Amperometric.c
RampTest.c
Reset.c
Measurement_Core.c
Measurement_Routines.c
AD5940_Platform.c
rp2040port.c
)
target_compile_definitions(EIS PRIVATE CHIPSEL_594X)
target_include_directories(EIS PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(EIS
pico_stdlib
hardware_spi
hardware_gpio
hardware_dma
hardware_irq
hardware_vreg
hardware_clocks
m
)
pico_enable_stdio_usb(EIS 1)
pico_enable_stdio_uart(EIS 0)
pico_add_extra_outputs(EIS)

View File

@ -0,0 +1,650 @@
/*!
*****************************************************************************
@file: Impedance.c
@author: Neo Xu
@brief: standard 4-wire or 2-wire impedance measurement sequences.
-----------------------------------------------------------------------------
Copyright (c) 2017-2019 Analog Devices, Inc. All Rights Reserved.
This software is proprietary to Analog Devices, Inc. and its licensors.
By using this software you agree to the terms of the associated
Analog Devices Software License Agreement.
*****************************************************************************/
#include "Impedance.h"
#include "ad5940.h"
#include "math.h"
#include "string.h"
#include <stdio.h>
/* Default LPDAC resolution(2.5V internal reference). */
#define DAC12BITVOLT_1LSB (2200.0f / 4095) // mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB * 64) // mV
static uint32_t const HpRtiaTable[] = {200, 1000, 5000, 10000, 20000,
40000, 80000, 160000, 0};
static float ResRtiaCal = 0.0f;
/*
Application configuration structure. Specified by user from template.
The variables are usable in this whole application.
It includes basic configuration for sequencer generator and application
related parameters
*/
AppIMPCfg_Type AppIMPCfg = {
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.ImpODR = 20.0, /* 20.0 Hz*/
.NumOfData = -1,
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.RcalVal = 10000.0,
.RtiaVal = 1000.0,
.ShortRe0Se0 = bFALSE,
.DswitchSel = SWD_CE0,
.PswitchSel = SWP_CE0,
.NswitchSel = SWN_AIN1,
.TswitchSel = SWT_AIN1,
.PwrMod = AFEPWR_HP,
.HstiaRtiaSel = HSTIARTIA_200,
.ExcitBufGain = EXCITBUFGAIN_2,
.HsDacGain = HSDACGAIN_1,
.HsDacUpdateRate = 7,
.DacVoltPP = 800.0,
.BiasVolt = 0.0f,
.SinFreq = 100000.0, /* 1000Hz */
.DftNum = DFTNUM_16384,
.DftSrc = DFTSRC_SINC3,
.HanWinEn = bTRUE,
.AdcPgaGain = ADCPGA_1,
.ADCSinc3Osr = ADCSINC3OSR_2,
.ADCSinc2Osr = ADCSINC2OSR_22,
.ADCAvgNum = ADCAVGNUM_16,
.SweepCfg.SweepEn = bTRUE,
.SweepCfg.SweepStart = 1000.0,
.SweepCfg.SweepStop = 200000.0,
.SweepCfg.SweepPoints = 101,
.SweepCfg.SweepLog = bFALSE,
.SweepCfg.SweepIndex = 0,
.FifoThresh = 4,
.IMPInited = bFALSE,
.StopRequired = bFALSE,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
int32_t AppIMPGetCfg(void *pCfg) {
if (pCfg) {
*(AppIMPCfg_Type **)pCfg = &AppIMPCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
int32_t AppIMPCtrl(uint32_t Command, void *pPara) {
switch (Command) {
case IMPCTRL_START: {
WUPTCfg_Type wupt_cfg;
if (AD5940_WakeUp(10) >
10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if (AppIMPCfg.IMPInited == bFALSE)
return AD5940ERR_APPERROR;
/* Start it */
wupt_cfg.WuptEn = bTRUE;
wupt_cfg.WuptEndSeq = WUPTENDSEQ_A;
wupt_cfg.WuptOrder[0] = SEQID_0;
wupt_cfg.SeqxSleepTime[SEQID_0] = 4;
wupt_cfg.SeqxWakeupTime[SEQID_0] =
(uint32_t)(AppIMPCfg.WuptClkFreq / AppIMPCfg.ImpODR) - 4;
AD5940_WUPTCfg(&wupt_cfg);
AppIMPCfg.FifoDataCount = 0; /* restart */
break;
}
case IMPCTRL_STOPNOW: {
if (AD5940_WakeUp(10) >
10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Start Wupt right now */
AD5940_WUPTCtrl(bFALSE);
/* There is chance this operation will fail because sequencer could put AFE
back to hibernate mode just after waking up. Use STOPSYNC is better. */
AD5940_WUPTCtrl(bFALSE);
break;
}
case IMPCTRL_STOPSYNC: {
AppIMPCfg.StopRequired = bTRUE;
break;
}
case IMPCTRL_GETFREQ: {
if (pPara == 0)
return AD5940ERR_PARA;
if (AppIMPCfg.SweepCfg.SweepEn == bTRUE)
*(float *)pPara = AppIMPCfg.FreqofData;
else
*(float *)pPara = AppIMPCfg.SinFreq;
} break;
case IMPCTRL_SHUTDOWN: {
AppIMPCtrl(IMPCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
/* Turn off LPloop related blocks which are not controlled automatically by
* hibernate operation */
AFERefCfg_Type aferef_cfg;
LPLoopCfg_Type lp_loop;
memset(&aferef_cfg, 0, sizeof(aferef_cfg));
AD5940_REFCfgS(&aferef_cfg);
memset(&lp_loop, 0, sizeof(lp_loop));
AD5940_LPLoopCfgS(&lp_loop);
AD5940_EnterSleepS(); /* Enter Hibernate */
} break;
default:
break;
}
return AD5940ERR_OK;
}
/* generated code snnipet */
float AppIMPGetCurrFreq(void) {
if (AppIMPCfg.SweepCfg.SweepEn == bTRUE)
return AppIMPCfg.FreqofData;
else
return AppIMPCfg.SinFreq;
}
/* Application initialization */
static AD5940Err AppIMPSeqCfgGen(void) {
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type HsLoopCfg;
DSPCfg_Type dsp_cfg;
float sin_freq;
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Init all to disable state */
aferef_cfg.HpBandgapEn = bTRUE;
aferef_cfg.Hp1V1BuffEn = bTRUE;
aferef_cfg.Hp1V8BuffEn = bTRUE;
aferef_cfg.Disc1V1Cap = bFALSE;
aferef_cfg.Disc1V8Cap = bFALSE;
aferef_cfg.Hp1V8ThemBuff = bFALSE;
aferef_cfg.Hp1V8Ilimit = bFALSE;
aferef_cfg.Lp1V1BuffEn = bFALSE;
aferef_cfg.Lp1V8BuffEn = bFALSE;
/* LP reference control - turn off them to save power*/
aferef_cfg.LpBandgapEn = bFALSE;
aferef_cfg.LpRefBufEn = bFALSE;
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
HsLoopCfg.HsDacCfg.ExcitBufGain = AppIMPCfg.ExcitBufGain;
HsLoopCfg.HsDacCfg.HsDacGain = AppIMPCfg.HsDacGain;
HsLoopCfg.HsDacCfg.HsDacUpdateRate = AppIMPCfg.HsDacUpdateRate;
HsLoopCfg.HsTiaCfg.DiodeClose = bFALSE;
HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
HsLoopCfg.HsTiaCfg.HstiaCtia = 31; /* 31pF + 2pF */
HsLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
HsLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
HsLoopCfg.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel;
HsLoopCfg.SWMatCfg.Dswitch = AppIMPCfg.DswitchSel;
HsLoopCfg.SWMatCfg.Pswitch = AppIMPCfg.PswitchSel;
HsLoopCfg.SWMatCfg.Nswitch = AppIMPCfg.NswitchSel;
HsLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA | AppIMPCfg.TswitchSel;
HsLoopCfg.WgCfg.WgType = WGTYPE_SIN;
HsLoopCfg.WgCfg.GainCalEn = bTRUE;
HsLoopCfg.WgCfg.OffsetCalEn = bTRUE;
if (AppIMPCfg.SweepCfg.SweepEn == bTRUE) {
AppIMPCfg.FreqofData = AppIMPCfg.SweepCfg.SweepStart;
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepCfg.SweepStart;
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
sin_freq = AppIMPCfg.SweepCurrFreq;
} else {
sin_freq = AppIMPCfg.SinFreq;
AppIMPCfg.FreqofData = sin_freq;
}
HsLoopCfg.WgCfg.SinCfg.SinFreqWord =
AD5940_WGFreqWordCal(sin_freq, AppIMPCfg.SysClkFreq);
HsLoopCfg.WgCfg.SinCfg.SinAmplitudeWord =
(uint32_t)(AppIMPCfg.DacVoltPP / 800.0f * 2047 + 0.5f);
HsLoopCfg.WgCfg.SinCfg.SinOffsetWord = 0;
HsLoopCfg.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&HsLoopCfg);
/* LPDAC configuration removed - using HSTIA internal bias */
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppIMPCfg.AdcPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = AppIMPCfg.ADCAvgNum;
dsp_cfg.ADCFilterCfg.ADCRate =
ADCRATE_800KHZ; /* Tell filter block clock rate of ADC*/
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.DftCfg.DftNum = AppIMPCfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppIMPCfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppIMPCfg.HanWinEn;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg));
AD5940_DSPCfgS(&dsp_cfg);
/* Enable all of them. They are automatically turned off during hibernate mode
* to save power */
/* Enable all of them. They are automatically turned off during hibernate mode
* to save power */
AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR |
AFECTRL_WG | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR |
AFECTRL_SINC2NOTCH,
bTRUE);
/* Sequence end. */
AD5940_SEQGenInsert(SEQ_STOP()); /* Add one extra command to disable sequencer
for initialization sequence because we
only want it to run one time. */
/* Stop here */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if (error == AD5940ERR_OK) {
AppIMPCfg.InitSeqInfo.SeqId = SEQID_1;
AppIMPCfg.InitSeqInfo.SeqRamAddr = AppIMPCfg.SeqStartAddr;
AppIMPCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppIMPCfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppIMPCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
} else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppIMPSeqMeasureGen(void) {
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
SWMatrixCfg_Type sw_cfg;
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_DFT;
clks_cal.DftSrc = AppIMPCfg.DftSrc;
clks_cal.DataCount = 1L << (AppIMPCfg.DftNum + 2); /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = AppIMPCfg.ADCAvgNum;
clks_cal.RatioSys2AdcClk = AppIMPCfg.SysClkFreq / AppIMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(
AGPIO_Pin2); /* Set GPIO1, clear others that under control */
AD5940_SEQGenInsert(SEQ_WAIT(16 * 250)); /* @todo wait 250us? */
/* Configure matrix for RCAL measurement */
sw_cfg.Dswitch = SWD_RCAL0;
sw_cfg.Pswitch = SWP_RCAL0;
sw_cfg.Nswitch = SWN_RCAL1;
sw_cfg.Tswitch = SWT_RCAL1 | SWT_TRTIA;
AD5940_SWMatrixCfgS(&sw_cfg);
// Turn on Excitation/ADC
AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR |
AFECTRL_DACREFPWR | AFECTRL_HSDACPWR | AFECTRL_SINC2NOTCH,
bTRUE);
AD5940_AFECtrlS(AFECTRL_ADCPWR | AFECTRL_WG, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(16 * 10)); // Settling
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks));
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT | AFECTRL_WG | AFECTRL_ADCPWR,
bFALSE);
/* Configure matrix for external Rz (Sensor) */
sw_cfg.Dswitch = AppIMPCfg.DswitchSel;
sw_cfg.Pswitch = AppIMPCfg.PswitchSel;
sw_cfg.Nswitch = AppIMPCfg.NswitchSel;
sw_cfg.Tswitch = SWT_TRTIA | AppIMPCfg.TswitchSel;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_AFECtrlS(AFECTRL_ADCPWR | AFECTRL_WG, bTRUE);
/* Enable Waveform generator */
AD5940_SEQGenInsert(SEQ_WAIT(16 * 10)); // Settling
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT,
bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT | AFECTRL_WG | AFECTRL_ADCPWR,
bFALSE); /* Stop ADC convert and DFT */
AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR |
AFECTRL_WG | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR |
AFECTRL_SINC2NOTCH,
bFALSE);
AD5940_SEQGpioCtrlS(0); /* Clr GPIO1 */
AD5940_EnterSleepS(); /* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if (error == AD5940ERR_OK) {
AppIMPCfg.MeasureSeqInfo.SeqId = SEQID_0;
AppIMPCfg.MeasureSeqInfo.SeqRamAddr =
AppIMPCfg.InitSeqInfo.SeqRamAddr + AppIMPCfg.InitSeqInfo.SeqLen;
AppIMPCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppIMPCfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppIMPCfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
} else
return error; /* Error */
return AD5940ERR_OK;
}
/* This function provide application initialize. It can also enable Wupt that
* will automatically trigger sequence. Or it can configure */
int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize) {
AD5940Err error = AD5940ERR_OK;
SEQCfg_Type seq_cfg;
FIFOCfg_Type fifo_cfg;
if (AD5940_WakeUp(10) >
10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Configure sequencer and stop it */
seq_cfg.SeqMemSize =
SEQMEMSIZE_2KB; /* 2kB SRAM is used for sequencer, others for data FIFO */
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bTRUE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Reconfigure FIFO */
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE); /* Disable FIFO firstly */
fifo_cfg.FIFOEn = bTRUE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize =
FIFOSIZE_4KB; /* 4kB for FIFO, The reset 2kB for sequencer */
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
fifo_cfg.FIFOThresh = AppIMPCfg.FifoThresh;
AD5940_FIFOCfg(&fifo_cfg);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Start sequence generator */
/* Initialize sequencer generator */
if ((AppIMPCfg.IMPInited == bFALSE) || (AppIMPCfg.bParaChanged == bTRUE)) {
if (pBuffer == 0)
return AD5940ERR_PARA;
if (BufferSize == 0)
return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppIMPSeqCfgGen(); /* Application initialization sequence using
either MCU or sequencer */
if (error != AD5940ERR_OK)
return error;
/* Generate measurement sequence */
error = AppIMPSeqMeasureGen();
if (error != AD5940ERR_OK)
return error;
AppIMPCfg.bParaChanged = bFALSE; /* Clear this flag as we already
implemented the new configuration */
}
/* Initialization sequencer */
AppIMPCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppIMPCfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppIMPCfg.InitSeqInfo.SeqId);
while (AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE)
;
/* Measurement sequence */
AppIMPCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppIMPCfg.MeasureSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AppIMPCfg.PwrMod, AFEBW_250KHZ);
AppIMPCfg.IMPInited = bTRUE; /* IMP application has been initialized. */
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
int32_t AppIMPRegModify(int32_t *const pData, uint32_t *pDataCount) {
if (AppIMPCfg.NumOfData > 0) {
AppIMPCfg.FifoDataCount += *pDataCount / 4; // Changed from /2 to /4
if (AppIMPCfg.FifoDataCount >= AppIMPCfg.NumOfData) {
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if (AppIMPCfg.StopRequired == bTRUE) {
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
if (AppIMPCfg.SweepCfg
.SweepEn) /* Need to set new frequency and set power mode */
{
AD5940_WGFreqCtrlS(AppIMPCfg.SweepNextFreq, AppIMPCfg.SysClkFreq);
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return
* back to controller */
int32_t AppIMPDataProcess(int32_t *const pData, uint32_t *pDataCount) {
uint32_t DataCount = *pDataCount;
uint32_t ImpResCount = DataCount / 4;
fImpPol_Type *const pOut = (fImpPol_Type *)pData;
iImpCar_Type *pSrcData = (iImpCar_Type *)pData;
*pDataCount = 0;
DataCount = (DataCount / 4) * 4; // Align 4
/* Convert DFT result to int32_t type */
for (uint32_t i = 0; i < DataCount; i++) {
pData[i] &= 0x3ffff; /* @todo option to check ECC */
if (pData[i] & (1L << 17)) /* Bit17 is sign bit */
{
pData[i] |= 0xfffc0000; /* Data is 18bit in two's complement, bit17 is the
sign bit */
}
}
for (uint32_t i = 0; i < ImpResCount; i++) {
iImpCar_Type *pDftRcal, *pDftRz;
pDftRcal = pSrcData++;
pDftRz = pSrcData++;
float RzMag, RzPhase;
float RcalMag, RcalPhase;
// Calculate RCAL (Reference)
RcalMag = sqrt((float)pDftRcal->Real * pDftRcal->Real +
(float)pDftRcal->Image * pDftRcal->Image);
RcalPhase = atan2(-pDftRcal->Image, pDftRcal->Real);
// Calculate Sensor
RzMag = sqrt((float)pDftRz->Real * pDftRz->Real +
(float)pDftRz->Image * pDftRz->Image);
RzPhase = atan2(-pDftRz->Image, pDftRz->Real);
// Determines Gain Factor for Calibration logic (though mostly ratiometric
// acts via ratio)
// Formula: Rz = Rcal * (V_rz_out / V_rcal_out) ? No.
// Formula: Rz = (V_excite / I)
// I = V_rcal_out / Rcal_gain_resistor ?? No.
// Standard Ratiometric: Rz = Rcal_val * (Mag_Rcal_Meas / Mag_Rz_Meas)
// (Assuming constant current or ratio of voltages).
// Let's use the standard formula from original code which accounts for
// everything via Ratio.
// Note: This relies on Gain being CONSTANT between RCAL measure and Sensor
// measure. Since we don't change Gain settings in the sequence (only MUX),
// this holds true.
RzMag = (RcalMag / RzMag) * AppIMPCfg.RcalVal;
RzPhase = RcalPhase - RzPhase;
pOut[i].Magnitude = RzMag;
pOut[i].Phase = RzPhase; // Phase drift should cancel out via Ratiometric?
// Original code did RcalPhase - RzPhase.
}
*pDataCount = ImpResCount;
AppIMPCfg.FreqofData = AppIMPCfg.SweepCurrFreq;
/* Calculate next frequency point */
if (AppIMPCfg.SweepCfg.SweepEn == bTRUE) {
AppIMPCfg.FreqofData = AppIMPCfg.SweepCurrFreq;
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepNextFreq;
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
}
return 0;
}
/**
*/
int32_t AppIMPISR(void *pBuff, uint32_t *pCount) {
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
*pCount = 0;
if (AD5940_WakeUp(10) >
10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* Prohibit AFE to enter sleep mode. */
if (AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE) {
/* Now there should be 4 data in FIFO */
FifoCnt = (AD5940_FIFOGetCnt() / 4) * 4;
if (FifoCnt > BuffCount) {
///@todo buffer is limited.
}
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppIMPRegModify(pBuff,
&FifoCnt); /* If there is need to do AFE re-configure, do it
here when AFE is in active state */
// AD5940_EnterSleepS(); /* Manually put AFE back to hibernate mode. This
// operation only takes effect when register value is ACTIVE previously */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter sleep mode. */
/* Process data */
AppIMPDataProcess((int32_t *)pBuff, &FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}
// Added for compatibility with Measurement_Routines.c
void AppIMPCleanup(void) {
// Ensure chip is awake before sending commands
if (AD5940_WakeUp(10) > 10)
return;
// Stop Sequencer and Wakeup Timer
AD5940_WUPTCtrl(bFALSE);
AD5940_SEQCtrlS(bFALSE);
// Stop active conversions and Waveform Generator, keep Reference/LDOs on
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT | AFECTRL_WG, bFALSE);
// Reset FIFO configuration
FIFOCfg_Type fifo_cfg;
fifo_cfg.FIFOEn = bFALSE;
AD5940_FIFOCfg(&fifo_cfg);
fifo_cfg.FIFOEn = bTRUE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize = FIFOSIZE_4KB;
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
fifo_cfg.FIFOThresh = 4; // Match the new threshold
AD5940_FIFOCfg(&fifo_cfg);
// Clear all interrupt flags
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
}
// Added for compatibility with Measurement_Routines.c
AD5940Err AppIMPRtiaCal(void) {
HSRTIACal_Type hs_cal;
fImpPol_Type res;
AD5940Err error;
hs_cal.fFreq = AppIMPCfg.SinFreq;
hs_cal.fRcal = AppIMPCfg.RcalVal;
hs_cal.SysClkFreq = AppIMPCfg.SysClkFreq;
hs_cal.AdcClkFreq = AppIMPCfg.AdcClkFreq;
hs_cal.HsTiaCfg.DiodeClose = bFALSE;
hs_cal.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hs_cal.HsTiaCfg.HstiaCtia = 31;
hs_cal.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hs_cal.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hs_cal.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel;
hs_cal.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
hs_cal.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
hs_cal.DftCfg.DftNum = AppIMPCfg.DftNum;
hs_cal.DftCfg.DftSrc = AppIMPCfg.DftSrc;
hs_cal.DftCfg.HanWinEn = AppIMPCfg.HanWinEn;
hs_cal.bPolarResult = bTRUE;
hs_cal.bPolarResult = bTRUE;
error = AD5940_HSRtiaCal(&hs_cal, &res);
if (error == AD5940ERR_OK) {
ResRtiaCal = res.Magnitude;
// AppIMPCfg.RtiaVal = ResRtiaCal; // Removed as it was custom addition
printf("Calibrated RTIA: %f Ohm, Phase: %f\n", res.Magnitude, res.Phase);
} else
printf("RTIA Calibration Failed: %d\n", error);
return error;
}

View File

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

View File

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

View File

@ -0,0 +1,368 @@
// 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 = 4;
pImpedanceCfg->DacVoltPP = 800.0;
pImpedanceCfg->ExcitBufGain = EXCITBUFGAIN_2;
pImpedanceCfg->HsDacGain = HSDACGAIN_1;
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)
pImpedanceCfg->NswitchSel = SWN_SE0;
// T-Switch: Connect SE0LOAD to T-Node (Effectively SE0 with RLOAD=0)
pImpedanceCfg->TswitchSel = SWT_SE0LOAD;
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->LptiaRtiaSel = GetLPTIARtia(ConfigLptiaVal);
pAMPCfg->LpTiaRl = LPTIARLOAD_SHORT;
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->LPTIARtiaSel = GetLPTIARtia(ConfigLptiaVal);
pRampCfg->LPTIARloadSel = LPTIARLOAD_SHORT;
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.LpTiaRf = CurrentLpTiaRf;
lp_loop.LpAmpCfg.LpTiaRload = LPTIARLOAD_SHORT;
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_0R;
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;
// T-Switch: Connect SE0LOAD to T-Node
HpLoopCfg.SWMatCfg.Tswitch = SWT_SE0LOAD;
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);
}

View File

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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,415 @@
# Call Graph: ad5940
## Common Infrastructure
> Functions called > 5 times. Shown once here.
### AD5940_ReadAfeResult
└── AD5940_ReadAfeResult
└── #FF4500 **AD5940_ReadReg** ->>
### __AD5940_TakeMeasurement
└── #00FFFF **__AD5940_TakeMeasurement**
├── #008000 **AD5940_AFECtrlS** ->>
├── #FFD700 **AD5940_Delay10us** ->>
├── AD5940_INTCClrFlag ->>
├── AD5940_INTCTestFlag ->>
└── AD5940_ReadAfeResult ->>
### AD5940_HSLoopCfgS
└── AD5940_HSLoopCfgS
├── AD5940_HSDacCfgS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_HSTIACfgS
│ ├── #FF0000 **AD5940_WriteReg** ->>
│ └── __AD5940_SetDExRTIA
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_SWMatrixCfgS
│ └── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_WGCfgS
└── #FF0000 **AD5940_WriteReg** ->>
### AD5940_ADCFilterCfgS
└── AD5940_ADCFilterCfgS
├── #008000 **AD5940_AFECtrlS** ->>
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
### AD5940_ReadReg
└── #FF4500 **AD5940_ReadReg**
├── AD5940_D2DReadReg
├── AD5940_SEQReadReg
│ ├── AD5940_SEQGenGetRegDefault
│ │ ├── AD5940_D2DReadReg
│ │ └── AD5940_SPIReadReg
│ │ ├── AD5940_CsClr ->>
│ │ ├── AD5940_CsSet ->>
│ │ ├── AD5940_ReadWrite16B ->>
│ │ ├── AD5940_ReadWrite32B ->>
│ │ └── AD5940_ReadWrite8B ->>
│ ├── AD5940_SEQGenSearchReg
│ └── AD5940_SEQRegInfoInsert
└── AD5940_SPIReadReg
├── AD5940_CsClr ->>
├── AD5940_CsSet ->>
├── AD5940_ReadWrite16B ->>
├── AD5940_ReadWrite32B ->>
└── AD5940_ReadWrite8B ->>
### AD5940_WriteReg
└── #FF0000 **AD5940_WriteReg**
├── AD5940_D2DWriteReg
├── AD5940_SEQWriteReg
│ ├── AD5940_SEQGenInsert
│ ├── AD5940_SEQGenSearchReg
│ └── AD5940_SEQRegInfoInsert
└── AD5940_SPIWriteReg
├── AD5940_CsClr ->>
├── AD5940_CsSet ->>
├── AD5940_ReadWrite16B ->>
├── AD5940_ReadWrite32B ->>
└── AD5940_ReadWrite8B ->>
### AD5940_ADCMuxCfgS
└── AD5940_ADCMuxCfgS
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
### AD5940_INTCGetCfg
└── AD5940_INTCGetCfg
└── #FF4500 **AD5940_ReadReg** ->>
### AD5940_AFECtrlS
└── #008000 **AD5940_AFECtrlS**
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
### AD5940_INTCCfg
└── #0000FF **AD5940_INTCCfg**
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
### AD5940_ReadWrite16B
└── AD5940_ReadWrite16B
└── AD5940_ReadWriteNBytes
### AD5940_ReadWrite8B
└── AD5940_ReadWrite8B
└── AD5940_ReadWriteNBytes
### __AD5940_ReferenceON
└── __AD5940_ReferenceON
└── AD5940_REFCfgS
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
### AD5940_INTCTestFlag
└── AD5940_INTCTestFlag
└── #FF4500 **AD5940_ReadReg** ->>
### AD5940_INTCClrFlag
└── AD5940_INTCClrFlag
└── #FF0000 **AD5940_WriteReg** ->>
### AD5940_ADCBaseCfgS
└── #8B00FF **AD5940_ADCBaseCfgS**
└── #FF0000 **AD5940_WriteReg** ->>
### AD5940_ReadWrite32B
└── AD5940_ReadWrite32B
└── AD5940_ReadWriteNBytes
## Execution Tree
└── AD5940_ADCPGACal
├── #8B00FF **AD5940_ADCBaseCfgS** ->>
├── #008000 **AD5940_AFECtrlS** ->>
├── #FFD700 **AD5940_Delay10us** ->>
├── #0000FF **AD5940_INTCCfg** ->>
├── #FF4500 **AD5940_ReadReg** ->>
├── #FF0000 **AD5940_WriteReg** ->>
├── #00FFFF **__AD5940_TakeMeasurement** ->>
├── AD5940_ADCConvtCtrlS
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_ADCFilterCfgS ->>
├── AD5940_HSLoopCfgS ->>
├── AD5940_INTCGetCfg ->>
└── __AD5940_ReferenceON ->>
└── AD5940_ADCPowerCtrlS
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_ADCRepeatCfgS
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_AGPIOCfg
├── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_AGPIOFuncCfg
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_AGPIOIen
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_AGPIOOen
│ └── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_AGPIOPen
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_AGPIOClr
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_AGPIOIn
└── #FF4500 **AD5940_ReadReg** ->>
└── AD5940_AGPIOSet
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_AGPIOToggle
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_ClksCalculate
├── AD5940_Notch50HzAvailable
│ └── _is_value_in_table ->>
└── AD5940_Notch60HzAvailable
└── _is_value_in_table ->>
└── AD5940_FIFOCfg
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_FIFOCtrlS
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_FIFOGetCfg
└── #FF4500 **AD5940_ReadReg** ->>
└── AD5940_FIFOGetCnt
└── #FF4500 **AD5940_ReadReg** ->>
└── AD5940_FIFORd
├── AD5940_CsClr ->>
├── AD5940_CsSet ->>
├── AD5940_ReadWrite16B ->>
├── AD5940_ReadWrite32B ->>
└── AD5940_ReadWrite8B ->>
└── AD5940_FIFOThrshSet
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_GetADIID
└── #FF4500 **AD5940_ReadReg** ->>
└── AD5940_GetChipID
└── #FF4500 **AD5940_ReadReg** ->>
└── AD5940_HPModeEn
├── #FF4500 **AD5940_ReadReg** ->>
├── AD5940_AFEPwrBW
│ └── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_CLKCfg
├── #FFD700 **AD5940_Delay10us** ->>
├── #FF4500 **AD5940_ReadReg** ->>
├── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_HFOSC32MHzCtrl
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_HSDACCal
├── #8B00FF **AD5940_ADCBaseCfgS** ->>
├── #008000 **AD5940_AFECtrlS** ->>
├── #FFD700 **AD5940_Delay10us** ->>
├── #0000FF **AD5940_INTCCfg** ->>
├── #FF0000 **AD5940_WriteReg** ->>
├── #00FFFF **__AD5940_TakeMeasurement** ->>
├── AD5940_ADCCode2Volt
├── AD5940_ADCConvtCtrlS
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_ADCFilterCfgS ->>
├── AD5940_HSLoopCfgS ->>
├── AD5940_LPLoopCfgS
│ ├── AD5940_LPAMPCfgS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDACCfgS
│ ├── #FF0000 **AD5940_WriteReg** ->>
│ ├── AD5940_LPDAC0WriteS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDAC1WriteS
│ └── #FF0000 **AD5940_WriteReg** ->>
└── __AD5940_ReferenceON ->>
└── AD5940_HSRTIACfgS
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_HSRtiaCal
├── #008000 **AD5940_AFECtrlS** ->>
├── #FFD700 **AD5940_Delay10us** ->>
├── #0000FF **AD5940_INTCCfg** ->>
├── AD5940_ADCMuxCfgS ->>
├── AD5940_ComplexDivInt
├── AD5940_ComplexMag
├── AD5940_ComplexPhase
├── AD5940_DSPCfgS
│ ├── #8B00FF **AD5940_ADCBaseCfgS** ->>
│ ├── AD5940_ADCDigCompCfgS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ ├── AD5940_ADCFilterCfgS ->>
│ ├── AD5940_DFTCfgS
│ │ ├── #FF4500 **AD5940_ReadReg** ->>
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_StatisticCfgS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_HSLoopCfgS ->>
├── AD5940_INTCClrFlag ->>
├── AD5940_INTCGetCfg ->>
├── AD5940_INTCTestFlag ->>
├── AD5940_REFCfgS
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_ReadAfeResult ->>
├── AD5940_StructInit ->>
└── AD5940_WGFreqWordCal
└── AD5940_HWReset
├── #FFD700 **AD5940_Delay10us** ->>
├── AD5940_RstClr
└── AD5940_RstSet
└── AD5940_INTCGetFlag
└── #FF4500 **AD5940_ReadReg** ->>
└── AD5940_Initialize
├── #FF4500 **AD5940_ReadReg** ->>
├── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_CsSet ->>
└── AD5940_LFOSCMeasure
├── #0000FF **AD5940_INTCCfg** ->>
├── AD5940_INTCClrFlag ->>
├── AD5940_INTCGetCfg ->>
├── AD5940_INTCTestFlag ->>
├── AD5940_SEQCfg
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_SEQCtrlS
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_SEQGetCfg
│ └── #FF4500 **AD5940_ReadReg** ->>
├── AD5940_SEQInfoCfg
│ ├── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_SEQCmdWrite
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_SEQTimeOutRd
│ └── #FF4500 **AD5940_ReadReg** ->>
├── AD5940_WUPTCfg
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_WUPTCtrl
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_WUPTTime
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_LPDACCal
├── #8B00FF **AD5940_ADCBaseCfgS** ->>
├── #008000 **AD5940_AFECtrlS** ->>
├── #FFD700 **AD5940_Delay10us** ->>
├── #0000FF **AD5940_INTCCfg** ->>
├── #00FFFF **__AD5940_TakeMeasurement** ->>
├── AD5940_ADCConvtCtrlS
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_ADCFilterCfgS ->>
├── AD5940_ADCMuxCfgS ->>
├── AD5940_INTCGetCfg ->>
├── AD5940_LPDACCfgS
│ ├── #FF0000 **AD5940_WriteReg** ->>
│ ├── AD5940_LPDAC0WriteS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDAC1WriteS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_LPDACWriteS
│ └── #FF0000 **AD5940_WriteReg** ->>
└── __AD5940_ReferenceON ->>
└── AD5940_LPModeClkS
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_LPModeCtrlS
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_LPModeEnS
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_LPRtiaCal
├── #8B00FF **AD5940_ADCBaseCfgS** ->>
├── #008000 **AD5940_AFECtrlS** ->>
├── #FFD700 **AD5940_Delay10us** ->>
├── #0000FF **AD5940_INTCCfg** ->>
├── #FF4500 **AD5940_ReadReg** ->>
├── #FF0000 **AD5940_WriteReg** ->>
├── #00FFFF **__AD5940_TakeMeasurement** ->>
├── AD5940_ComplexDivInt
├── AD5940_ComplexMag
├── AD5940_ComplexPhase
├── AD5940_DSPCfgS
│ ├── #8B00FF **AD5940_ADCBaseCfgS** ->>
│ ├── AD5940_ADCDigCompCfgS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ ├── AD5940_ADCFilterCfgS ->>
│ ├── AD5940_DFTCfgS
│ │ ├── #FF4500 **AD5940_ReadReg** ->>
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_StatisticCfgS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_HSLoopCfgS ->>
├── AD5940_INTCClrFlag ->>
├── AD5940_INTCGetCfg ->>
├── AD5940_INTCTestFlag ->>
├── AD5940_LPLoopCfgS
│ ├── AD5940_LPAMPCfgS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDACCfgS
│ ├── #FF0000 **AD5940_WriteReg** ->>
│ ├── AD5940_LPDAC0WriteS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDAC1WriteS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_ReadAfeResult ->>
├── AD5940_SWMatrixCfgS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_StructInit ->>
├── AD5940_WGDACCodeS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_WGFreqWordCal
└── __AD5940_ReferenceON ->>
└── AD5940_LPTIAOffsetCal
├── #8B00FF **AD5940_ADCBaseCfgS** ->>
├── #008000 **AD5940_AFECtrlS** ->>
├── #FFD700 **AD5940_Delay10us** ->>
├── #0000FF **AD5940_INTCCfg** ->>
├── #FF0000 **AD5940_WriteReg** ->>
├── #00FFFF **__AD5940_TakeMeasurement** ->>
├── AD5940_ADCConvtCtrlS
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_ADCFilterCfgS ->>
├── AD5940_INTCGetCfg ->>
├── AD5940_LPLoopCfgS
│ ├── AD5940_LPAMPCfgS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDACCfgS
│ ├── #FF0000 **AD5940_WriteReg** ->>
│ ├── AD5940_LPDAC0WriteS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDAC1WriteS
│ └── #FF0000 **AD5940_WriteReg** ->>
└── __AD5940_ReferenceON ->>
└── AD5940_SEQGpioCtrlS
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_SEQGpioTrigCfg
├── #FF4500 **AD5940_ReadReg** ->>
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_SEQHaltS
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_SEQInfoGet
└── #FF4500 **AD5940_ReadReg** ->>
└── AD5940_SEQMmrTrig
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_ShutDownS
├── AD5940_EnterSleepS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_LPLoopCfgS
│ ├── AD5940_LPAMPCfgS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDACCfgS
│ ├── #FF0000 **AD5940_WriteReg** ->>
│ ├── AD5940_LPDAC0WriteS
│ │ └── #FF0000 **AD5940_WriteReg** ->>
│ └── AD5940_LPDAC1WriteS
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_REFCfgS
│ ├── #FF4500 **AD5940_ReadReg** ->>
│ └── #FF0000 **AD5940_WriteReg** ->>
├── AD5940_SleepKeyCtrlS
│ └── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_StructInit ->>
└── AD5940_SoftRst
├── #FFD700 **AD5940_Delay10us** ->>
└── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_WGFreqCtrlS
├── #FF0000 **AD5940_WriteReg** ->>
└── AD5940_WGFreqWordCal
└── AD5940_WakeUp
└── #FF4500 **AD5940_ReadReg** ->>

6
examples/rp2040_port/build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
rm -rf build
mkdir build
cd build
cmake ..
make -j4

7
examples/rp2040_port/host/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
build/
build_android/
build_ios/
build_macos/
build_windows/
icons
*.png

View File

@ -0,0 +1,265 @@
# host/CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(EISConfigurator VERSION 1.0 LANGUAGES CXX C)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
# --- FOR WINDOWS MSVC ERRORS ---
if(MSVC)
add_compile_options($<$<COMPILE_LANGUAGE:C>:/std:clatest>)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()
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 ---
# ==========================================
# --- FFTW3 Configuration (Double Precision) ---
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)
# Enhanced NEON detection for Windows on Arm as well
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)
elseif(MSVC AND CMAKE_SYSTEM_PROCESSOR MATCHES "(ARM64|arm64|aarch64)")
set(ENABLE_NEON ON CACHE BOOL "Enable NEON" FORCE)
endif()
# Only apply sed patch on UNIX-like systems
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
)
FetchContent_MakeAvailable(QCustomPlot)
# ==========================================
# --- ICON GENERATION ---
# ==========================================
set(ICON_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/assets/icon_source.png")
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)
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 ---
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}")
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
)
# 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()
# --- Mobile Definitions ---
if(BUILD_ANDROID OR BUILD_IOS)
target_compile_definitions(EISConfigurator PRIVATE IS_MOBILE)
endif()
# --- 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()
target_link_libraries(EISConfigurator PRIVATE Qt6::Widgets Qt6::SerialPort Qt6::PrintSupport)
if(APPLE)
target_link_libraries(EISConfigurator PRIVATE ${FFTW_TARGET})
endif()
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"
MACOSX_BUNDLE_GUI_IDENTIFIER "com.eis.configurator"
MACOSX_BUNDLE_ICON_FILE "app_icon.icns"
RESOURCE "${MACOS_ICON}"
)
elseif(WIN32)
set_target_properties(EISConfigurator PROPERTIES
WIN32_EXECUTABLE TRUE
)
target_link_libraries(EISConfigurator PRIVATE
Qt6::Core Qt6::Gui Qt6::Widgets Qt6::SerialPort Qt6::PrintSupport
${FFTW_TARGET}
)
endif()
qt_finalize_executable(EISConfigurator)

View File

@ -0,0 +1,95 @@
# host/Makefile
QT_ANDROID_KIT ?= $(HOME)/Qt/6.8.3/android_arm64_v8a
QT_IOS_KIT ?= $(HOME)/Qt/6.8.3/ios
QT_MACOS_PATH ?= /opt/homebrew/opt/qt@6
QT_WIN_PATH ?= C:/Qt/6.8.3/msvc2019_64
BUILD_DIR_MACOS = build_macos
BUILD_DIR_WIN = build_windows
BUILD_DIR_ANDROID = build_android
BUILD_DIR_IOS = build_ios
TARGET = EISConfigurator
# Android Specifics
PKG_NAME = org.qtproject.example.EISConfigurator
# 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
desktop: macos
macos:
@echo "Building for macOS (Apple Silicon)..."
@mkdir -p $(BUILD_DIR_MACOS)
@cd $(BUILD_DIR_MACOS) && cmake .. \
-DCMAKE_PREFIX_PATH="$(QT_MACOS_PATH);/opt/homebrew" \
-DCMAKE_BUILD_TYPE=Release
@$(MAKE) -C $(BUILD_DIR_MACOS)
@echo "Build Complete. Run with: open $(BUILD_DIR_MACOS)/$(TARGET).app"
windows:
@echo "Building for Windows..."
@mkdir -p $(BUILD_DIR_WIN)
@cd $(BUILD_DIR_WIN) && cmake .. \
-DCMAKE_PREFIX_PATH="$(QT_WIN_PATH)" \
-DCMAKE_BUILD_TYPE=Release
@cmake --build $(BUILD_DIR_WIN)
android:
@if [ ! -d "$(QT_ANDROID_KIT)" ]; then echo "Error: QT_ANDROID_KIT not found at $(QT_ANDROID_KIT)"; exit 1; fi
@mkdir -p $(BUILD_DIR_ANDROID)
@cd $(BUILD_DIR_ANDROID) && cmake .. \
-DCMAKE_TOOLCHAIN_FILE=$(QT_ANDROID_KIT)/lib/cmake/Qt6/qt.toolchain.cmake \
-DQT_ANDROID_ABIS="arm64-v8a" \
-DANDROID_PLATFORM=android-24 \
-DQT_ANDROID_BUILD_ALL_ABIS=OFF \
-DBUILD_ANDROID=ON \
-DCMAKE_BUILD_TYPE=Debug
@cmake --build $(BUILD_DIR_ANDROID) --target apk
@echo "APK generated at $(APK_PATH)"
ios:
@if [ ! -d "$(QT_IOS_KIT)" ]; then echo "Error: QT_IOS_KIT not found at $(QT_IOS_KIT)"; exit 1; fi
@mkdir -p $(BUILD_DIR_IOS)
@echo "Configuring iOS CMake..."
@cd $(BUILD_DIR_IOS) && cmake .. -G Xcode \
-DCMAKE_TOOLCHAIN_FILE=$(QT_IOS_KIT)/lib/cmake/Qt6/qt.toolchain.cmake \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_DEPLOYMENT_TARGET=16.0 \
-DBUILD_IOS=ON
@echo "iOS Project generated at $(BUILD_DIR_IOS)/$(TARGET).xcodeproj"
run:
@open $(BUILD_DIR_MACOS)/$(TARGET).app
# --- Android Deployment Wrappers ---
install_android: android
@echo "Installing $(APK_PATH) to device..."
@adb install -r $(APK_PATH)
run_android: install_android
@echo "Launching $(PKG_NAME)..."
@adb shell am start -n $(PKG_NAME)/org.qtproject.qt.android.bindings.QtActivity
debug_android: run_android
@echo "Attaching Logcat (Ctrl+C to exit)..."
@adb logcat -v color -s EISConfigurator Qt:* DEBUG
# --- Cleaning ---
clean:
@echo "Cleaning build artifacts..."
@if [ -f "$(BUILD_DIR_MACOS)/Makefile" ]; then cmake --build $(BUILD_DIR_MACOS) --target clean; fi
@if [ -f "$(BUILD_DIR_WIN)/Makefile" ] || [ -f "$(BUILD_DIR_WIN)/build.ninja" ]; then cmake --build $(BUILD_DIR_WIN) --target clean; fi
@if [ -f "$(BUILD_DIR_ANDROID)/Makefile" ] || [ -f "$(BUILD_DIR_ANDROID)/build.ninja" ]; then cmake --build $(BUILD_DIR_ANDROID) --target clean; fi
@if [ -d "$(BUILD_DIR_IOS)/$(TARGET).xcodeproj" ]; then cmake --build $(BUILD_DIR_IOS) --target clean; fi
distclean:
@rm -rf $(BUILD_DIR_MACOS) $(BUILD_DIR_WIN) $(BUILD_DIR_ANDROID) $(BUILD_DIR_IOS)
@echo "Removed all build directories."
.PHONY: all desktop macos windows android ios run install_android run_android debug_android clean distclean

View File

@ -0,0 +1,42 @@
<?xml version="1.0"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application android:label="EIS Configurator"
android:name="org.qtproject.qt.android.bindings.QtApplication"
android:requestLegacyExternalStorage="true"
android:icon="@mipmap/ic_launcher"
android:extractNativeLibs="true">
<activity android:name="org.qtproject.qt.android.bindings.QtActivity"
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"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- 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>

View File

@ -0,0 +1,27 @@
{
"images" : [
{ "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-20@2x.png", "scale" : "2x" },
{ "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-20@3x.png", "scale" : "3x" },
{ "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-29.png", "scale" : "1x" },
{ "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-29@2x.png", "scale" : "2x" },
{ "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-29@3x.png", "scale" : "3x" },
{ "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-40@2x.png", "scale" : "2x" },
{ "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-40@3x.png", "scale" : "3x" },
{ "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-60@2x.png", "scale" : "2x" },
{ "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-60@3x.png", "scale" : "3x" },
{ "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-20.png", "scale" : "1x" },
{ "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-20@2x.png", "scale" : "2x" },
{ "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-29.png", "scale" : "1x" },
{ "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-29@2x.png", "scale" : "2x" },
{ "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-40.png", "scale" : "1x" },
{ "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-40@2x.png", "scale" : "2x" },
{ "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-76.png", "scale" : "1x" },
{ "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-76@2x.png", "scale" : "2x" },
{ "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-83.5@2x.png", "scale" : "2x" },
{ "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-1024.png", "scale" : "1x" }
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>Yr Crystals</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiresFullScreen</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<!-- Permissions -->
<key>NSMicrophoneUsageDescription</key>
<string>This app requires audio access to visualize music.</string>
<key>NSAppleMusicUsageDescription</key>
<string>This app requires access to your music library to play tracks.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires access to the photo library to load album art.</string>
<key>NSCameraUsageDescription</key>
<string>This app requires camera access for visualizer input.</string>
<!-- File Access -->
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<!-- Document Types -->
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Audio</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>public.audio</string>
<string>public.mp3</string>
<string>public.mpeg-4-audio</string>
<string>public.folder</string>
<string>public.directory</string>
<string>public.content</string>
<string>public.data</string>
</array>
</dict>
</array>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,108 @@
@echo off
setlocal enabledelayedexpansion
:: ==============================================================================
:: PATHS
:: ==============================================================================
:: This script lives in scripts/
set "SCRIPT_DIR=%~dp0"
:: The build scripts live in windows/
pushd "%SCRIPT_DIR%..\windows"
set "WINDOWS_SCRIPTS_DIR=%CD%"
popd
:: The output dir is build_windows/
pushd "%SCRIPT_DIR%.."
set "PROJECT_ROOT=%CD%"
popd
set "OUTPUT_ROOT=%PROJECT_ROOT%\build_windows"
echo [MASTER] Project Root: %PROJECT_ROOT%
echo [MASTER] Scripts Dir: %WINDOWS_SCRIPTS_DIR%
echo [MASTER] Output Dir: %OUTPUT_ROOT%
echo.
:: ==============================================================================
:: 1. BUILD ARM64
:: ==============================================================================
echo [MASTER] Starting ARM64 Build...
echo ------------------------------------------------------------------------------
:: Navigate to the windows directory so the script runs in its native context
pushd "%WINDOWS_SCRIPTS_DIR%"
:: Run the script
call build_arm64.bat
if %errorlevel% neq 0 (
echo.
echo [MASTER] CRITICAL ERROR: ARM64 Build Failed.
popd
pause
exit /b %errorlevel%
)
:: Return to master context
popd
echo.
:: ==============================================================================
:: 2. ZIP ARM64
:: ==============================================================================
echo [MASTER] Packaging ARM64...
set "ARM64_SOURCE=%OUTPUT_ROOT%\arm64"
set "ARM64_ZIP=%OUTPUT_ROOT%\YrCrystals_Win_ARM64.zip"
if exist "%ARM64_SOURCE%\YrCrystals.exe" (
if exist "%ARM64_ZIP%" del "%ARM64_ZIP%"
powershell -NoProfile -Command "Compress-Archive -Path '%ARM64_SOURCE%\*' -DestinationPath '%ARM64_ZIP%' -Force"
echo [SUCCESS] Created: %ARM64_ZIP%
) else (
echo [ERROR] ARM64 Output directory missing or empty. Skipping Zip.
)
echo.
:: ==============================================================================
:: 3. BUILD x64
:: ==============================================================================
echo [MASTER] Starting x64 Build...
echo ------------------------------------------------------------------------------
pushd "%WINDOWS_SCRIPTS_DIR%"
call build_x64.bat
if %errorlevel% neq 0 (
echo.
echo [MASTER] CRITICAL ERROR: x64 Build Failed.
popd
pause
exit /b %errorlevel%
)
popd
echo.
:: ==============================================================================
:: 4. ZIP x64
:: ==============================================================================
echo [MASTER] Packaging x64...
set "X64_SOURCE=%OUTPUT_ROOT%\x64"
set "X64_ZIP=%OUTPUT_ROOT%\YrCrystals_Win_x64.zip"
if exist "%X64_SOURCE%\YrCrystals.exe" (
if exist "%X64_ZIP%" del "%X64_ZIP%"
powershell -NoProfile -Command "Compress-Archive -Path '%X64_SOURCE%\*' -DestinationPath '%X64_ZIP%' -Force"
echo [SUCCESS] Created: %X64_ZIP%
) else (
echo [ERROR] x64 Output directory missing or empty. Skipping Zip.
)
:: ==============================================================================
:: DONE
:: ==============================================================================
echo.
echo ------------------------------------------------------------------------------
echo [MASTER] All builds completed successfully.
echo [OUTPUT] %OUTPUT_ROOT%
echo ------------------------------------------------------------------------------
pause

View File

@ -0,0 +1,52 @@
@echo off
setlocal
:: Arguments passed from CMake:
:: %1 = Path to magick.exe
:: %2 = Source Image
:: %3 = Destination Icon
set "MAGICK_EXE=%~1"
set "SOURCE_IMG=%~2"
set "DEST_ICO=%~3"
:: -- FOR MISSING DELEGATES / REGISTRY ERRORS ---
for %%I in ("%MAGICK_EXE%") do set "MAGICK_DIR=%%~dpI"
if "%MAGICK_DIR:~-1%"=="\" set "MAGICK_DIR=%MAGICK_DIR:~0,-1%"
set "MAGICK_HOME=%MAGICK_DIR%"
set "MAGICK_CONFIGURE_PATH=%MAGICK_DIR%"
set "MAGICK_CODER_MODULE_PATH=%MAGICK_DIR%\modules\coders"
:: ---------------------------------------------------
:: 1. Validate Source
if not exist "%SOURCE_IMG%" (
echo [ERROR] Icon source not found at: %SOURCE_IMG%
exit /b 1
)
:: 2. Ensure Destination Directory Exists
if not exist "%~dp3" mkdir "%~dp3"
:: 3. Generate the .ico (Nearest Neighbor / Pixel Art Mode)
echo [ICONS] Generating Pixel-Perfect Windows Icon: %DEST_ICO%
:: We use -sample (Nearest Neighbor) explicitly for each standard icon size.
:: We clone the original (index 0) for every resize to ensure maximum accuracy from the source.
"%MAGICK_EXE%" "%SOURCE_IMG%" ^
-background none -alpha on ^
( -clone 0 -sample 256x256 ) ^
( -clone 0 -sample 128x128 ) ^
( -clone 0 -sample 64x64 ) ^
( -clone 0 -sample 48x48 ) ^
( -clone 0 -sample 32x32 ) ^
( -clone 0 -sample 16x16 ) ^
-delete 0 ^
"%DEST_ICO%"
if %errorlevel% neq 0 (
echo [ERROR] ImageMagick failed to generate icon.
exit /b %errorlevel%
)
echo [SUCCESS] Icon generated.
exit /b 0

View File

@ -0,0 +1,144 @@
#!/bin/bash
# Argument 1: Path to magick executable
MAGICK_BIN="$1"
# Fallback if not provided
if [ -z "$MAGICK_BIN" ]; then
MAGICK_BIN="magick"
fi
# Assumes running from Project Root
SOURCE="assets/icon_source.png"
if [ ! -f "$SOURCE" ]; then
echo "Error: Source image '$SOURCE' not found in $(pwd)"
exit 1
fi
# Verify magick works
"$MAGICK_BIN" -version >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Error: ImageMagick tool '$MAGICK_BIN' not found or not working."
exit 1
fi
# --- macOS ---
# Keep macOS icons in assets/icons as they are linked explicitly
MACOS_OUT_DIR="assets/icons"
mkdir -p "$MACOS_OUT_DIR"
ICONSET="$MACOS_OUT_DIR/icon.iconset"
mkdir -p "$ICONSET"
echo "[ICONS] Generating macOS iconset (Pixel Perfect)..."
"$MAGICK_BIN" "$SOURCE" -sample 16x16 "$ICONSET/icon_16x16.png"
"$MAGICK_BIN" "$SOURCE" -sample 32x32 "$ICONSET/icon_16x16@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 32x32 "$ICONSET/icon_32x32.png"
"$MAGICK_BIN" "$SOURCE" -sample 64x64 "$ICONSET/icon_32x32@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 128x128 "$ICONSET/icon_128x128.png"
"$MAGICK_BIN" "$SOURCE" -sample 256x256 "$ICONSET/icon_128x128@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 256x256 "$ICONSET/icon_256x256.png"
"$MAGICK_BIN" "$SOURCE" -sample 512x512 "$ICONSET/icon_256x256@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 512x512 "$ICONSET/icon_512x512.png"
"$MAGICK_BIN" "$SOURCE" -sample 1024x1024 "$ICONSET/icon_512x512@2x.png"
iconutil -c icns "$ICONSET" -o "$MACOS_OUT_DIR/app_icon.icns"
rm -rf "$ICONSET"
# --- Windows ---
echo "[ICONS] Generating Windows .ico (Pixel Perfect)..."
# Updated to match the high-quality logic from the Windows Batch file
# (Removed -alpha off and -colors 256 which would ruin transparency)
"$MAGICK_BIN" "$SOURCE" \
-background none -alpha on \
\( -clone 0 -sample 256x256 \) \
\( -clone 0 -sample 128x128 \) \
\( -clone 0 -sample 64x64 \) \
\( -clone 0 -sample 48x48 \) \
\( -clone 0 -sample 32x32 \) \
\( -clone 0 -sample 16x16 \) \
-delete 0 "$MACOS_OUT_DIR/app_icon.ico"
# --- Android ---
# Output directly to android/res so QT_ANDROID_PACKAGE_SOURCE_DIR picks it up
echo "[ICONS] Generating Android mipmaps (Pixel Perfect)..."
ANDROID_DIR="android/res"
mkdir -p "$ANDROID_DIR/mipmap-mdpi"
"$MAGICK_BIN" "$SOURCE" -sample 48x48 "$ANDROID_DIR/mipmap-mdpi/ic_launcher.png"
mkdir -p "$ANDROID_DIR/mipmap-hdpi"
"$MAGICK_BIN" "$SOURCE" -sample 72x72 "$ANDROID_DIR/mipmap-hdpi/ic_launcher.png"
mkdir -p "$ANDROID_DIR/mipmap-xhdpi"
"$MAGICK_BIN" "$SOURCE" -sample 96x96 "$ANDROID_DIR/mipmap-xhdpi/ic_launcher.png"
mkdir -p "$ANDROID_DIR/mipmap-xxhdpi"
"$MAGICK_BIN" "$SOURCE" -sample 144x144 "$ANDROID_DIR/mipmap-xxhdpi/ic_launcher.png"
mkdir -p "$ANDROID_DIR/mipmap-xxxhdpi"
"$MAGICK_BIN" "$SOURCE" -sample 192x192 "$ANDROID_DIR/mipmap-xxxhdpi/ic_launcher.png"
# --- iOS ---
# Output directly to ios/Assets.xcassets
echo "[ICONS] Generating iOS AppIcon (Pixel Perfect)..."
XCASSETS_DIR="ios/Assets.xcassets"
IOS_DIR="$XCASSETS_DIR/AppIcon.appiconset"
mkdir -p "$IOS_DIR"
cat > "$XCASSETS_DIR/Contents.json" <<EOF
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
EOF
"$MAGICK_BIN" "$SOURCE" -sample 20x20 "$IOS_DIR/Icon-20.png"
"$MAGICK_BIN" "$SOURCE" -sample 40x40 "$IOS_DIR/Icon-20@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 60x60 "$IOS_DIR/Icon-20@3x.png"
"$MAGICK_BIN" "$SOURCE" -sample 29x29 "$IOS_DIR/Icon-29.png"
"$MAGICK_BIN" "$SOURCE" -sample 58x58 "$IOS_DIR/Icon-29@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 87x87 "$IOS_DIR/Icon-29@3x.png"
"$MAGICK_BIN" "$SOURCE" -sample 40x40 "$IOS_DIR/Icon-40.png"
"$MAGICK_BIN" "$SOURCE" -sample 80x80 "$IOS_DIR/Icon-40@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 120x120 "$IOS_DIR/Icon-40@3x.png"
"$MAGICK_BIN" "$SOURCE" -sample 120x120 "$IOS_DIR/Icon-60@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 180x180 "$IOS_DIR/Icon-60@3x.png"
"$MAGICK_BIN" "$SOURCE" -sample 76x76 "$IOS_DIR/Icon-76.png"
"$MAGICK_BIN" "$SOURCE" -sample 152x152 "$IOS_DIR/Icon-76@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 167x167 "$IOS_DIR/Icon-83.5@2x.png"
"$MAGICK_BIN" "$SOURCE" -sample 1024x1024 "$IOS_DIR/Icon-1024.png"
cat > "$IOS_DIR/Contents.json" <<EOF
{
"images" : [
{ "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-20@2x.png", "scale" : "2x" },
{ "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-20@3x.png", "scale" : "3x" },
{ "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-29.png", "scale" : "1x" },
{ "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-29@2x.png", "scale" : "2x" },
{ "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-29@3x.png", "scale" : "3x" },
{ "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-40@2x.png", "scale" : "2x" },
{ "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-40@3x.png", "scale" : "3x" },
{ "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-60@2x.png", "scale" : "2x" },
{ "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-60@3x.png", "scale" : "3x" },
{ "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-20.png", "scale" : "1x" },
{ "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-20@2x.png", "scale" : "2x" },
{ "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-29.png", "scale" : "1x" },
{ "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-29@2x.png", "scale" : "2x" },
{ "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-40.png", "scale" : "1x" },
{ "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-40@2x.png", "scale" : "2x" },
{ "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-76.png", "scale" : "1x" },
{ "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-76@2x.png", "scale" : "2x" },
{ "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-83.5@2x.png", "scale" : "2x" },
{ "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-1024.png", "scale" : "1x" }
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
EOF
echo "[SUCCESS] All platform icons generated."

View File

@ -0,0 +1,534 @@
// File: host/src/GraphWidget.cpp
#include "GraphWidget.h"
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QInputDialog>
#include <QMenu>
#include <cmath>
#include <limits>
// Simple Linear Regression Helper
static void linearRegression(const QVector<double> &x, const QVector<double> &y,
double &m, double &c) {
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
int n = x.size();
for (int i = 0; i < n; i++) {
sumX += x[i];
sumY += y[i];
sumXY += x[i] * y[i];
sumX2 += x[i] * x[i];
}
m = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
c = (sumY - m * sumX) / n;
}
GraphWidget::GraphWidget(QWidget *parent) : QWidget(parent) {
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);
plot->setBackground(QBrush(QColor(25, 25, 25)));
auto styleAxis = [](QCPAxis *axis) {
axis->setBasePen(QPen(Qt::white));
axis->setTickPen(QPen(Qt::white));
axis->setSubTickPen(QPen(Qt::white));
axis->setTickLabelColor(Qt::white);
axis->setLabelColor(Qt::white);
axis->grid()->setPen(QPen(QColor(60, 60, 60), 0, Qt::DotLine));
axis->grid()->setSubGridVisible(true);
axis->grid()->setSubGridPen(QPen(QColor(40, 40, 40), 0, Qt::DotLine));
};
styleAxis(plot->xAxis);
styleAxis(plot->yAxis);
styleAxis(plot->yAxis2);
// --- Setup Graphs ---
graphReal = plot->addGraph();
graphReal->setPen(QPen(QColor(0, 255, 255), 2));
graphReal->setLineStyle(QCPGraph::lsLine);
graphReal->setScatterStyle(
QCPScatterStyle(QCPScatterStyle::ssCircle, QColor(0, 255, 255), 3));
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;
plot->yAxis2->setVisible(true);
plot->yAxis2->setTickLabels(true);
connect(plot->yAxis, SIGNAL(rangeChanged(QCPRange)), plot->yAxis2,
SLOT(setRange(QCPRange)));
connect(plot->yAxis2, SIGNAL(rangeChanged(QCPRange)), plot->yAxis,
SLOT(setRange(QCPRange)));
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);
plot->legend->setVisible(true);
QFont legendFont = font();
legendFont.setPointSize(9);
plot->legend->setFont(legendFont);
plot->legend->setBrush(QBrush(QColor(40, 40, 40, 200)));
plot->legend->setBorderPen(QPen(Qt::white));
plot->legend->setTextColor(Qt::white);
selectionRect = new QCPItemRect(plot);
selectionRect->setPen(QPen(Qt::yellow, 1, Qt::DashLine));
selectionRect->setBrush(QBrush(QColor(255, 255, 0, 50)));
selectionRect->setVisible(false);
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::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;
// 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");
plot->yAxis->setLabel("Magnitude (Ohms)");
plot->yAxis->setScaleType(QCPAxis::stLogarithmic);
QSharedPointer<QCPAxisTickerLog> logTickerY(new QCPAxisTickerLog);
plot->yAxis->setTicker(logTickerY);
plot->yAxis->setNumberFormat("eb");
plot->yAxis2->setLabel("Phase (Rad)");
plot->yAxis2->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->yAxis2->setTicker(linTicker);
plot->yAxis2->setNumberFormat("f");
plot->yAxis2->setVisible(true);
graphReal->setName("Magnitude");
graphImag->setName("Phase");
graphHilbert->setName("Hilbert");
graphReal->setVisible(true);
graphImag->setVisible(true);
graphHilbert->setVisible(true);
graphNyquistCorr->setVisible(false);
graphAmp->setVisible(false);
graphExtrapolated->setVisible(false);
graphLSVBlank->setVisible(false);
graphLSVSample->setVisible(false);
graphLSVDiff->setVisible(false);
graphFit->setVisible(false);
plot->replot();
}
void GraphWidget::configureNyquistPlot() {
plot->xAxis->setLabel("Real (Z')");
plot->xAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->xAxis->setTicker(linTicker);
plot->xAxis->setNumberFormat("f");
plot->yAxis->setLabel("-Imaginary (-Z'')");
plot->yAxis->setScaleType(QCPAxis::stLinear);
plot->yAxis->setTicker(linTicker);
plot->yAxis->setNumberFormat("f");
plot->yAxis2->setVisible(false);
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::configureAmperometricPlot() {
plot->xAxis->setLabel("Sample Index");
plot->xAxis->setScaleType(QCPAxis::stLinear);
QSharedPointer<QCPAxisTicker> linTicker(new QCPAxisTicker);
plot->xAxis->setTicker(linTicker);
plot->xAxis->setNumberFormat("f");
plot->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();
}
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;
}
if (target) {
target->addData(voltage, current);
target->rescaleAxes(false);
plot->replot();
}
}
void GraphWidget::addHilbertData(const QVector<double> &freq,
const QVector<double> &hilbertImag) {
if (plot->xAxis->label() != "Frequency (Hz)")
return;
graphHilbert->setData(freq, hilbertImag);
graphHilbert->rescaleAxes(false);
plot->replot();
}
void GraphWidget::setExtrapolatedPoint(double real, double imag) {
graphExtrapolated->data()->clear();
graphExtrapolated->addData(real, -imag);
plot->replot();
}
void GraphWidget::clear() {
graphReal->data()->clear();
graphImag->data()->clear();
graphHilbert->data()->clear();
graphNyquistCorr->data()->clear();
graphAmp->data()->clear();
graphExtrapolated->data()->clear();
graphFit->data()->clear();
plot->replot();
}
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();
}

View File

@ -0,0 +1,78 @@
// File: host/src/GraphWidget.h
#pragma once
#include <QWidget>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QDialog>
#include <QComboBox>
#include <QSpinBox>
#include <QCheckBox>
#include "qcustomplot.h"
class GraphWidget : public QWidget {
Q_OBJECT
public:
explicit GraphWidget(QWidget *parent = nullptr);
// Data Handling
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 *mainLayout;
QCustomPlot *plot;
// 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);
};

View File

@ -0,0 +1,61 @@
// File: host/src/MainWindow.cpp
#include "MainWindow.h"
#include <QGesture>
#include <QScroller>
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);
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);
grabGesture(Qt::SwipeGesture);
}
MainWindow::~MainWindow() {
if (serial->isOpen()) {
serial->close();
}
}
void MainWindow::loadSettings() {
cellConstant = settings->value("cellConstant", 1.0).toDouble();
}
void MainWindow::saveSettings() {
settings->setValue("cellConstant", cellConstant);
}
bool MainWindow::event(QEvent *event) {
if (event->type() == QEvent::Gesture) {
QGestureEvent *ge = static_cast<QGestureEvent *>(event);
if (QGesture *swipe = ge->gesture(Qt::SwipeGesture)) {
handleSwipe(static_cast<QSwipeGesture *>(swipe));
return true;
}
}
return QMainWindow::event(event);
}
void MainWindow::handleSwipe(QSwipeGesture *gesture) {
if (gesture->state() == Qt::GestureFinished) {
if (gesture->horizontalDirection() == QSwipeGesture::Left) {
if (mainTabWidget->currentIndex() < mainTabWidget->count() - 1)
mainTabWidget->setCurrentIndex(mainTabWidget->currentIndex() + 1);
} else if (gesture->horizontalDirection() == QSwipeGesture::Right) {
if (mainTabWidget->currentIndex() > 0)
mainTabWidget->setCurrentIndex(mainTabWidget->currentIndex() - 1);
}
}
}

View File

@ -0,0 +1,148 @@
// File: host/src/MainWindow.h
#pragma once
#include "GraphWidget.h"
#include <QCheckBox>
#include <QComboBox>
#include <QEvent>
#include <QGestureEvent>
#include <QGroupBox>
#include <QMainWindow>
#include <QMessageBox>
#include <QPushButton>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QSettings>
#include <QSwipeGesture>
#include <QTabWidget>
#include <QTextEdit>
#include <QTimer>
#include <QToolBar>
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
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);
// 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;
GraphWidget *nyquistGraph;
GraphWidget *ampGraph;
GraphWidget *lsvGraph;
QTextEdit *logWidget;
// Layout Containers
QTabWidget *mainTabWidget;
QTabWidget *impGraphTabs;
// --- Global Controls ---
QComboBox *portSelector;
QPushButton *connectBtn;
QPushButton *checkIdBtn;
QPushButton *calibrateBtn;
QComboBox *comboTiaRange;
// --- Impedance Controls ---
QDoubleSpinBox *spinSweepStart;
QDoubleSpinBox *spinSweepStop;
QSpinBox *spinSweepPPD;
QPushButton *sweepBtn;
QDoubleSpinBox *spinFreq;
QDoubleSpinBox *spinImpBias; // New: Impedance DC Bias
QPushButton *measureBtn;
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;
int expectedSweepPoints = 0;
enum LSVState { LSV_IDLE, LSV_RUNNING_BLANK, LSV_RUNNING_SAMPLE };
LSVState lsvState = LSV_IDLE;
// Data Accumulation
QVector<double> sweepFreqs;
QVector<double> sweepReals;
QVector<double> sweepImags;
struct LSVPoint {
double voltage;
double current;
};
QVector<LSVPoint> lsvBlankData;
QVector<LSVPoint> lsvSampleData;
};

View File

@ -0,0 +1,443 @@
// File: host/src/MainWindow_Actions.cpp
#include "MainWindow.h"
#include <cmath>
#include <complex>
#include <fftw3.h>
#include <vector>
void MainWindow::checkDeviceId() {
if (serial->isOpen())
serial->write("v\n");
}
void MainWindow::runCalibration() {
if (serial->isOpen()) {
int hpVal = comboTiaRange->currentData().toInt();
serial->write(QString("r %1\n").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());
}
}
// Removed: onShortRe0Se0Toggled
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();
double bias = spinImpBias->value(); // Use the Imp Bias control
// Calculate Total Points based on Decades + 1 for Artifact Removal
double decades = std::log10(stop / start);
if (decades < 0)
decades = std::abs(decades); // Handle swap
// Calculate intended total points (inclusive)
int totalPoints = (int)(decades * ppd) + 1;
if (totalPoints < 2)
totalPoints = 2;
expectedSweepPoints = totalPoints; // Store what we want to keep
// Send request for N+1 points to Firmware (Last point is artifact)
QString cmd = QString("s %1 %2 %3 %4")
.arg(start)
.arg(stop)
.arg(totalPoints + 1)
.arg(bias);
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();
double bias = spinImpBias->value();
QString cmd = QString("m %1 %2").arg(freq).arg(bias);
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();
}

View File

@ -0,0 +1,267 @@
// 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);
comboTiaRange->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);
comboTiaRange->setEnabled(true);
// 2. Enable Tabs
mainTabWidget->widget(0)->setEnabled(true);
mainTabWidget->widget(1)->setEnabled(true);
mainTabWidget->widget(2)->setEnabled(true);
// Sync LPF
onLPFChanged(comboLPF->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);
comboTiaRange->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 hpVal = comboTiaRange->currentData().toInt();
serial->write(QString("r %1\n").arg(hpVal).toUtf8());
serial->write(QString("t 0\n").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("RCAL,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("RCAL,")) {
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] == "RCAL" && parts.size() >= 4) {
QString type = parts[1];
if (type == "FAIL") {
logWidget->append(QString(">> RCAL FAIL: %1").arg(data));
return;
}
bool okM, okP;
double mag = parts[2].toDouble(&okM);
double phase = parts[3].toDouble(&okP);
if (okM && okP) {
logWidget->append(QString(">> Calibration [%1]: Mag=%.2f Phase=%.2f")
.arg(type)
.arg(mag)
.arg(phase));
// TODO: Store this if needed for UI display
}
return;
}
if (parts[0] == "DATA" && parts.size() >= 6) {
bool okF, okM, okP;
double freq = parts[1].toDouble(&okF);
double mag = parts[4].toDouble(&okM);
double phase = parts[5].toDouble(&okP);
if (okF && okM && okP) {
// 1. Plot Bode (Magnitude/Phase)
// Note: Phase from AD5940 is in Radians.
rawGraph->addBodeData(freq, mag, phase);
// 2. Convert to Cartesian for Nyquist (Real/Imaginary)
// Z = Mag * e^(j*Phase) = Mag * (cos(Phase) + j*sin(Phase))
// Note: Nyquist Plot expects Z' vs -Z''.
std::complex<double> z = std::polar(mag, phase);
double real = z.real();
double imag =
z.imag(); // This is Z''. GraphWidget negates it for the plot.
nyquistGraph->addNyquistData(real, imag, real, imag, false);
if (sweepFreqs.size() >= expectedSweepPoints) {
// Discard extra points (Artifact removal)
return;
}
sweepFreqs.append(freq);
sweepReals.append(real);
sweepImags.append(imag);
if (sweepReals.size() > 10 && sweepReals.size() % 10 == 0) {
computeHilbert();
performCircleFit();
}
}
}
}

View File

@ -0,0 +1,392 @@
// File: host/src/MainWindow_UI.cpp
#include "MainWindow.h"
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QScroller>
#include <QVBoxLayout>
void MainWindow::setupUi() {
QWidget *central = new QWidget(this);
setCentralWidget(central);
QVBoxLayout *mainLayout = new QVBoxLayout(central);
mainLayout->setContentsMargins(2, 2, 2, 2);
mainLayout->setSpacing(2);
// ========================================================================
// 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();
comboTiaRange = new QComboBox(this);
comboTiaRange->setToolTip("High Speed TIA Range");
comboTiaRange->addItem("100 Ω (RCAL)", 100);
comboTiaRange->addItem("200 Ω (Internal)", 200);
comboTiaRange->addItem("1 kΩ (Internal)", 1000);
// Removed >1k options to prevent saturation
comboTiaRange->setCurrentIndex(1); // Default 200 (Index 1)
calibrateBtn = new QPushButton("Calibrate HW", this);
calibrateBtn->setStyleSheet("background-color: rgba(255, 255, 0, 102); "
"color: white; font-weight: bold;");
// Removed: checkShortRe0Se0
hwLayout->addWidget(new QLabel("TIA Range:"));
hwLayout->addWidget(comboTiaRange, 1);
// Removed: 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");
// New Bias Control
spinImpBias = new QDoubleSpinBox();
spinImpBias->setRange(-3000.0, 3000.0);
spinImpBias->setValue(0.0);
spinImpBias->setSuffix(" mV");
spinImpBias->setToolTip("DC Bias Voltage");
measureBtn = new QPushButton("Measure");
measureBtn->setStyleSheet("background-color: rgba(0, 100, 0, 76); color: "
"white; font-weight: bold;");
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(new QLabel("Bias:"));
singleLayout->addWidget(spinImpBias);
singleLayout->addWidget(measureBtn);
// Removed Shunt controls
singleLayout->addWidget(new QLabel("Std:"));
singleLayout->addWidget(spinCondStd);
singleLayout->addWidget(btnCalCond);
impCtrlLayout->addLayout(singleLayout);
// 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(-3000.0, 3000.0);
spinAmpBias->setValue(0.0);
spinAmpBias->setSuffix(" mV");
comboLPF = new QComboBox();
comboLPF->addItem("Bypass", 0);
comboLPF->addItem("20kΩ (8Hz)", 1);
comboLPF->addItem("100kΩ (1.6Hz)", 2);
comboLPF->addItem("200kΩ (0.8Hz)", 3);
comboLPF->addItem("400kΩ (0.4Hz)", 4);
comboLPF->addItem("600kΩ (0.26Hz)", 5);
comboLPF->addItem("1MΩ (0.16Hz)", 6);
comboLPF->setCurrentIndex(1);
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(-3000.0, 3000.0);
spinLsvStart->setValue(800.0);
spinLsvStart->setSuffix(" mV");
spinLsvStop = new QDoubleSpinBox();
spinLsvStop->setRange(-3000.0, 3000.0);
spinLsvStop->setValue(-200.0);
spinLsvStop->setSuffix(" mV");
spinLsvSteps = new QSpinBox();
spinLsvSteps->setRange(10, 4000);
spinLsvSteps->setValue(200);
spinLsvSteps->setSuffix(" pts");
spinLsvDuration = new QSpinBox();
spinLsvDuration->setRange(100, 600000);
spinLsvDuration->setValue(10000);
spinLsvDuration->setSuffix(" ms");
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);
comboTiaRange->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(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(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;");
}
}

View File

@ -0,0 +1,92 @@
// 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");
// --- 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(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(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();
return app.exec();
}

175
examples/rp2040_port/main.c Normal file
View File

@ -0,0 +1,175 @@
// File: main.c
#include "App_Common.h"
// --- 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;
uint32_t ConfigLptiaVal = 100000;
uint32_t ConfigHstiaVal = 1000;
uint32_t CurrentLpTiaRf = LPTIARF_20K;
uint32_t ConfigRLoad = LPTIARLOAD_100R;
float CalibratedLptiaVal = 100000.0;
float CalibratedHstiaVal = 1000.0; // Default nominal
float CalibratedHstiaPhase = 0.0; // Default 0 phase shift
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 hp = 0;
int count = sscanf(input_buffer + 2, "%d", &hp);
if (count >= 1) {
ConfigHstiaVal = hp;
CalibratedHstiaVal =
(float)hp; // Fix: Reset calibration to nominal when range changes
}
printf("RANGE_SET HP:%d\n", ConfigHstiaVal);
}
} else if (cmd == 't') {
if (strlen(input_buffer) > 2) {
int val = atoi(input_buffer + 2);
GlobalShortRe0Se0 = (val > 0) ? bTRUE : bFALSE;
printf("SHORT_RE0_SE0:%d\n", GlobalShortRe0Se0);
}
} else if (cmd == 'c') {
Routine_CalibrateLFO();
Routine_CalibrateSystem();
} else if (cmd == 'm') {
float freq = 1000.0f;
float bias = 0.0f;
if (strlen(input_buffer) > 2) {
int count = sscanf(input_buffer + 2, "%f %f", &freq, &bias);
// If only one arg provided, freq is set, bias remains 0.
}
// Routine_Measure needs to handle bias now.
// We'll update Routine_Measure momentarily.
// For now, let's just pass freq as before, but update Routine_Measure
// signature.
Routine_Measure(freq, bias);
} else if (cmd == 's') {
float start = 100.0f, end = 100000.0f;
int steps = 50;
float bias = 0.0f;
if (strlen(input_buffer) > 2) {
int count =
sscanf(input_buffer + 2, "%f %f %d %f", &start, &end, &steps, &bias);
// defaults handled by initialization
}
Routine_Sweep(start, end, steps, bias);
} else if (cmd == 'a') {
float bias = 0.0f;
if (strlen(input_buffer) > 2)
bias = atof(input_buffer + 2);
Routine_Amperometric(bias);
} else if (cmd == 'l') {
float start = -500.0f, end = 500.0f;
int steps = 100, duration = 10000;
if (strlen(input_buffer) > 2)
sscanf(input_buffer + 2, "%f %f %d %d", &start, &end, &steps, &duration);
Routine_LSV(start, end, steps, duration);
} else if (cmd == 'x') {
if (CurrentMode == MODE_IMPEDANCE)
AppIMPCleanup();
else if (CurrentMode == MODE_AMPEROMETRIC)
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
else if (CurrentMode == MODE_RAMP)
AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
printf("STOPPED\n");
SystemReset();
} else if (cmd == 'z') {
SystemReset();
}
}
int main() {
stdio_init_all();
sleep_ms(2000);
setup_pins();
AD5940_HWReset();
AD5940_Initialize();
AD5940PlatformCfg();
Routine_CalibrateLFO();
printf("SYSTEM_READY\n");
while (true) {
int c = getchar_timeout_us(0);
if (c != PICO_ERROR_TIMEOUT) {
if (c == '\n' || c == '\r') {
input_buffer[input_pos] = 0;
if (input_pos > 0)
process_command();
input_pos = 0;
} else if (input_pos < 63) {
input_buffer[input_pos++] = (char)c;
}
}
if (gpio_get(PIN_INT) == 0) {
uint32_t temp = APPBUFF_SIZE;
int32_t status = 0;
if (CurrentMode == MODE_IMPEDANCE) {
status = AppIMPISR(AppBuff, &temp);
if (status == AD5940ERR_BUFF) {
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_BUFF) {
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_BUFF) {
printf("ERROR: FIFO Overflow/Underflow. Stopping.\n");
AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
SystemReset();
} else if (temp > 0) {
RampShowResult((float *)AppBuff, temp);
}
}
if (status == AD5940ERR_STOP) {
printf("STOPPED\n");
if (CurrentMode == MODE_IMPEDANCE)
AppIMPCleanup();
else if (CurrentMode == MODE_AMPEROMETRIC)
AppAMPCtrl(AMPCTRL_SHUTDOWN, 0);
else if (CurrentMode == MODE_RAMP)
AppRAMPCtrl(APPCTRL_SHUTDOWN, 0);
CurrentMode = MODE_IDLE;
SystemReset();
}
}
}
return 0;
}

View File

@ -0,0 +1,121 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

View File

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

View File

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

View File

@ -0,0 +1,181 @@
/*!
*****************************************************************************
@file: AD5940Main.c
@author: Neo Xu
@brief: Standard 4-wire or 2-wire impedance measurement example.
-----------------------------------------------------------------------------
Copyright (c) 2017-2019 Analog Devices, Inc. All Rights Reserved.
This software is proprietary to Analog Devices, Inc. and its licensors.
By using this software you agree to the terms of the associated
Analog Devices Software License Agreement.
*****************************************************************************/
#include "Impedance.h"
/**
User could configure following parameters
**/
#define APPBUFF_SIZE 512
uint32_t AppBuff[APPBUFF_SIZE];
int32_t ImpedanceShowResult(uint32_t *pData, uint32_t DataCount) {
float freq;
fImpPol_Type *pImp = (fImpPol_Type *)pData;
AppIMPCtrl(IMPCTRL_GETFREQ, &freq);
printf("Freq:%.2f ", freq);
/*Process data*/
for (int i = 0; i < DataCount; i++) {
printf("RzMag: %f Ohm , RzPhase: %f \n", pImp[i].Magnitude,
pImp[i].Phase * 180 / MATH_PI);
}
return 0;
}
static int32_t AD5940PlatformCfg(void) {
CLKCfg_Type clk_cfg;
FIFOCfg_Type fifo_cfg;
AGPIOCfg_Type gpio_cfg;
/* Use hardware reset */
AD5940_HWReset();
AD5940_Initialize();
/* Platform configuration */
/* Step1. Configure clock */
clk_cfg.ADCClkDiv = ADCCLKDIV_1;
clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC;
clk_cfg.SysClkDiv = SYSCLKDIV_1;
clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC;
clk_cfg.HfOSC32MHzMode = bFALSE;
clk_cfg.HFOSCEn = bTRUE;
clk_cfg.HFXTALEn = bFALSE;
clk_cfg.LFOSCEn = bTRUE;
AD5940_CLKCfg(&clk_cfg);
/* Step2. Configure FIFO and Sequencer*/
fifo_cfg.FIFOEn = bFALSE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize =
FIFOSIZE_4KB; /* 4kB for FIFO, The reset 2kB for sequencer */
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
fifo_cfg.FIFOThresh =
4; // AppIMPCfg.FifoThresh; /* DFT result. One pair for RCAL,
// another for Rz. One DFT result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg);
fifo_cfg.FIFOEn = bTRUE;
AD5940_FIFOCfg(&fifo_cfg);
/* Step3. Interrupt controller */
AD5940_INTCCfg(
AFEINTC_1, AFEINTSRC_ALLINT,
bTRUE); /* Enable all interrupt in INTC1, so we can check INTC flags */
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH, bTRUE);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Step4: Reconfigure GPIO */
gpio_cfg.FuncSet = GP0_INT | GP1_SLEEP | GP2_SYNC;
gpio_cfg.InputEnSet = 0;
gpio_cfg.OutputEnSet = AGPIO_Pin0 | AGPIO_Pin1 | AGPIO_Pin2;
gpio_cfg.OutVal = 0;
gpio_cfg.PullEnSet = 0;
AD5940_AGPIOCfg(&gpio_cfg);
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter sleep mode. */
return 0;
}
void AD5940ImpedanceStructInit(void) {
AppIMPCfg_Type *pImpedanceCfg;
AppIMPGetCfg(&pImpedanceCfg);
/* Step1: configure initialization sequence Info */
pImpedanceCfg->SeqStartAddr = 0;
pImpedanceCfg->MaxSeqLen = 512; /* @todo add checker in function */
pImpedanceCfg->RcalVal = 100.0;
pImpedanceCfg->SinFreq = 60000.0;
pImpedanceCfg->FifoThresh = 4;
/* Set switch matrix to onboard(EVAL-AD5940ELECZ) dummy sensor. */
/* Note the RCAL0 resistor is 10kOhm. */
/* MODIFIED: Using AIN2/AIN3 for voltage sense as per user setup (CE0, SE0,
* AIN2, AIN3) */
pImpedanceCfg->DswitchSel = SWD_CE0;
pImpedanceCfg->PswitchSel = SWP_AIN2;
pImpedanceCfg->NswitchSel = SWN_AIN3;
pImpedanceCfg->TswitchSel = SWT_SE0LOAD;
/* The dummy sensor is as low as 5kOhm. We need to make sure RTIA is small
* enough that HSTIA won't be saturated. */
pImpedanceCfg->HstiaRtiaSel = HSTIARTIA_1K;
/* Disable Hardware Sweep */
pImpedanceCfg->SweepCfg.SweepEn = bFALSE;
}
void AD5940_Main(void) {
uint32_t temp;
AD5940PlatformCfg();
AD5940ImpedanceStructInit();
AppIMPInit(AppBuff,
APPBUFF_SIZE); /* Initialize IMP application. Provide a buffer,
which is used to store sequencer commands */
AppIMPCtrl(IMPCTRL_START,
0); /* Control IMP measurement to start. Second parameter has no
meaning with this command. */
AppIMPCfg_Type *pImpedanceCfg;
AppIMPGetCfg(&pImpedanceCfg);
printf("\n--- EIS Configuration ---\n");
printf("RcalVal: %.2f Ohm\n", pImpedanceCfg->RcalVal);
printf("BiasVolt: %.2f mV\n", pImpedanceCfg->BiasVolt);
printf("HstiaRtiaSel: 0x%02X\n", pImpedanceCfg->HstiaRtiaSel);
printf("AC Volts PP: %.2f mV\n", pImpedanceCfg->DacVoltPP);
printf("-------------------------\n\n");
/* Calculate Sweep Points */
float start_freq = 100.0f;
float stop_freq = 100000.0f;
int num_points = 101;
// float log_step = pow(stop_freq / start_freq, 1.0 / (num_points - 1));
float log_step = 1.0715193f; // 1000^(1/100)
float curr_freq = start_freq;
printf("Starting Software Sweep: %.2f Hz to %.2f Hz, %d points\n", start_freq,
stop_freq, num_points);
for (int i = 0; i < num_points; i++) {
uint32_t temp;
/* Update Frequency */
pImpedanceCfg->SinFreq = curr_freq;
/* Re-Initialize App to apply new frequency (this regenerates sequences) */
AppIMPInit(AppBuff, APPBUFF_SIZE);
/* Calibrate RTIA at this frequency */
AppIMPRtiaCal();
/* Measure Impedance */
AppIMPCtrl(IMPCTRL_START, 0);
/* Wait for result */
while (1) {
if (AD5940_GetMCUIntFlag()) {
AD5940_ClrMCUIntFlag();
temp = APPBUFF_SIZE;
AppIMPISR(AppBuff, &temp);
ImpedanceShowResult(AppBuff, temp);
break; // Done with this point
}
}
/* Next Frequency */
curr_freq *= log_step;
}
printf("Sweep Completed.\n");
while (1)
; // Stop
}

View File

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

View File

@ -0,0 +1,660 @@
/*!
*****************************************************************************
@file: Impedance.c
@author: Neo Xu
@brief: standard 4-wire or 2-wire impedance measurement sequences.
-----------------------------------------------------------------------------
Copyright (c) 2017-2019 Analog Devices, Inc. All Rights Reserved.
This software is proprietary to Analog Devices, Inc. and its licensors.
By using this software you agree to the terms of the associated
Analog Devices Software License Agreement.
*****************************************************************************/
#include "Impedance.h"
#include "ad5940.h"
#include "math.h"
#include "string.h"
#include <stdio.h>
/* Default LPDAC resolution(2.5V internal reference). */
#define DAC12BITVOLT_1LSB (2200.0f / 4095) // mV
#define DAC6BITVOLT_1LSB (DAC12BITVOLT_1LSB * 64) // mV
static uint32_t const HpRtiaTable[] = {200, 1000, 5000, 10000, 20000,
40000, 80000, 160000, 0};
static float ResRtiaCal = 0.0f;
/*
Application configuration structure. Specified by user from template.
The variables are usable in this whole application.
It includes basic configuration for sequencer generator and application
related parameters
*/
AppIMPCfg_Type AppIMPCfg = {
.bParaChanged = bFALSE,
.SeqStartAddr = 0,
.MaxSeqLen = 0,
.SeqStartAddrCal = 0,
.MaxSeqLenCal = 0,
.ImpODR = 20.0, /* 20.0 Hz*/
.NumOfData = -1,
.SysClkFreq = 16000000.0,
.WuptClkFreq = 32000.0,
.AdcClkFreq = 16000000.0,
.RcalVal = 10000.0,
.DswitchSel = SWD_CE0,
.PswitchSel = SWP_CE0,
.NswitchSel = SWN_AIN1,
.TswitchSel = SWT_AIN1,
.PwrMod = AFEPWR_HP,
.HstiaRtiaSel = HSTIARTIA_1K,
.ExcitBufGain = EXCITBUFGAIN_2,
.HsDacGain = HSDACGAIN_1,
.HsDacUpdateRate = 7,
.DacVoltPP = 800.0,
.BiasVolt = -0.0f,
.SinFreq = 100000.0, /* 1000Hz */
.DftNum = DFTNUM_16384,
.DftSrc = DFTSRC_SINC3,
.HanWinEn = bTRUE,
.AdcPgaGain = ADCPGA_1,
.ADCSinc3Osr = ADCSINC3OSR_2,
.ADCSinc2Osr = ADCSINC2OSR_22,
.ADCAvgNum = ADCAVGNUM_16,
.SweepCfg.SweepEn = bTRUE,
.SweepCfg.SweepStart = 1000,
.SweepCfg.SweepStop = 100000.0,
.SweepCfg.SweepPoints = 101,
.SweepCfg.SweepLog = bFALSE,
.SweepCfg.SweepIndex = 0,
.FifoThresh = 4,
.IMPInited = bFALSE,
.StopRequired = bFALSE,
};
/**
This function is provided for upper controllers that want to change
application parameters specially for user defined parameters.
*/
int32_t AppIMPGetCfg(void *pCfg) {
if (pCfg) {
*(AppIMPCfg_Type **)pCfg = &AppIMPCfg;
return AD5940ERR_OK;
}
return AD5940ERR_PARA;
}
int32_t AppIMPCtrl(uint32_t Command, void *pPara) {
switch (Command) {
case IMPCTRL_START: {
WUPTCfg_Type wupt_cfg;
if (AD5940_WakeUp(10) >
10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
if (AppIMPCfg.IMPInited == bFALSE)
return AD5940ERR_APPERROR;
/* Start it */
wupt_cfg.WuptEn = bTRUE;
wupt_cfg.WuptEndSeq = WUPTENDSEQ_A;
wupt_cfg.WuptOrder[0] = SEQID_0;
wupt_cfg.SeqxSleepTime[SEQID_0] = 4;
wupt_cfg.SeqxWakeupTime[SEQID_0] =
(uint32_t)(AppIMPCfg.WuptClkFreq / AppIMPCfg.ImpODR) - 4;
AD5940_WUPTCfg(&wupt_cfg);
AppIMPCfg.FifoDataCount = 0; /* restart */
break;
}
case IMPCTRL_STOPNOW: {
if (AD5940_WakeUp(10) >
10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Start Wupt right now */
AD5940_WUPTCtrl(bFALSE);
/* There is chance this operation will fail because sequencer could put AFE
back to hibernate mode just after waking up. Use STOPSYNC is better. */
AD5940_WUPTCtrl(bFALSE);
break;
}
case IMPCTRL_STOPSYNC: {
AppIMPCfg.StopRequired = bTRUE;
break;
}
case IMPCTRL_GETFREQ: {
if (pPara == 0)
return AD5940ERR_PARA;
if (AppIMPCfg.SweepCfg.SweepEn == bTRUE)
*(float *)pPara = AppIMPCfg.FreqofData;
else
*(float *)pPara = AppIMPCfg.SinFreq;
} break;
case IMPCTRL_SHUTDOWN: {
AppIMPCtrl(IMPCTRL_STOPNOW, 0); /* Stop the measurement if it's running. */
/* Turn off LPloop related blocks which are not controlled automatically by
* hibernate operation */
AFERefCfg_Type aferef_cfg;
LPLoopCfg_Type lp_loop;
memset(&aferef_cfg, 0, sizeof(aferef_cfg));
AD5940_REFCfgS(&aferef_cfg);
memset(&lp_loop, 0, sizeof(lp_loop));
AD5940_LPLoopCfgS(&lp_loop);
AD5940_EnterSleepS(); /* Enter Hibernate */
} break;
default:
break;
}
return AD5940ERR_OK;
}
/* generated code snnipet */
float AppIMPGetCurrFreq(void) {
if (AppIMPCfg.SweepCfg.SweepEn == bTRUE)
return AppIMPCfg.FreqofData;
else
return AppIMPCfg.SinFreq;
}
/* Application initialization */
static AD5940Err AppIMPSeqCfgGen(void) {
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
AFERefCfg_Type aferef_cfg;
HSLoopCfg_Type HsLoopCfg;
DSPCfg_Type dsp_cfg;
float sin_freq;
/* Start sequence generator here */
AD5940_SEQGenCtrl(bTRUE);
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); /* Init all to disable state */
aferef_cfg.HpBandgapEn = bTRUE;
aferef_cfg.Hp1V1BuffEn = bTRUE;
aferef_cfg.Hp1V8BuffEn = bTRUE;
aferef_cfg.Disc1V1Cap = bFALSE;
aferef_cfg.Disc1V8Cap = bFALSE;
aferef_cfg.Hp1V8ThemBuff = bFALSE;
aferef_cfg.Hp1V8Ilimit = bFALSE;
aferef_cfg.Lp1V1BuffEn = bFALSE;
aferef_cfg.Lp1V8BuffEn = bFALSE;
/* LP reference control - turn off them to save power*/
if (AppIMPCfg.BiasVolt != 0.0f) /* With bias voltage */
{
aferef_cfg.LpBandgapEn = bTRUE;
aferef_cfg.LpRefBufEn = bTRUE;
} else {
aferef_cfg.LpBandgapEn = bFALSE;
aferef_cfg.LpRefBufEn = bFALSE;
}
aferef_cfg.LpRefBoostEn = bFALSE;
AD5940_REFCfgS(&aferef_cfg);
HsLoopCfg.HsDacCfg.ExcitBufGain = AppIMPCfg.ExcitBufGain;
HsLoopCfg.HsDacCfg.HsDacGain = AppIMPCfg.HsDacGain;
HsLoopCfg.HsDacCfg.HsDacUpdateRate = AppIMPCfg.HsDacUpdateRate;
HsLoopCfg.HsTiaCfg.DiodeClose = bFALSE;
if (AppIMPCfg.BiasVolt != 0.0f) /* With bias voltage */
HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_VZERO0;
else
HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
HsLoopCfg.HsTiaCfg.HstiaCtia = 31; /* 31pF + 2pF */
HsLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
HsLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
HsLoopCfg.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel;
HsLoopCfg.SWMatCfg.Dswitch = AppIMPCfg.DswitchSel;
HsLoopCfg.SWMatCfg.Pswitch = AppIMPCfg.PswitchSel;
HsLoopCfg.SWMatCfg.Nswitch = AppIMPCfg.NswitchSel;
HsLoopCfg.SWMatCfg.Tswitch = SWT_TRTIA | AppIMPCfg.TswitchSel;
HsLoopCfg.WgCfg.WgType = WGTYPE_SIN;
HsLoopCfg.WgCfg.GainCalEn = bTRUE;
HsLoopCfg.WgCfg.OffsetCalEn = bTRUE;
if (AppIMPCfg.SweepCfg.SweepEn == bTRUE) {
AppIMPCfg.FreqofData = AppIMPCfg.SweepCfg.SweepStart;
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepCfg.SweepStart;
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
sin_freq = AppIMPCfg.SweepCurrFreq;
} else {
sin_freq = AppIMPCfg.SinFreq;
AppIMPCfg.FreqofData = sin_freq;
}
HsLoopCfg.WgCfg.SinCfg.SinFreqWord =
AD5940_WGFreqWordCal(sin_freq, AppIMPCfg.SysClkFreq);
HsLoopCfg.WgCfg.SinCfg.SinAmplitudeWord =
(uint32_t)(AppIMPCfg.DacVoltPP / 800.0f * 2047 + 0.5f);
HsLoopCfg.WgCfg.SinCfg.SinOffsetWord = 0;
HsLoopCfg.WgCfg.SinCfg.SinPhaseWord = 0;
AD5940_HSLoopCfgS(&HsLoopCfg);
if (AppIMPCfg.BiasVolt != 0.0f) /* With bias voltage */
{
LPDACCfg_Type lpdac_cfg;
lpdac_cfg.LpdacSel = LPDAC0;
lpdac_cfg.LpDacVbiasMux =
LPDACVBIAS_12BIT; /* Use Vbias to tuning BiasVolt. */
lpdac_cfg.LpDacVzeroMux = LPDACVZERO_6BIT; /* Vbias-Vzero = BiasVolt */
lpdac_cfg.DacData6Bit = 0x40 >> 1; /* Set Vzero to middle scale. */
if (AppIMPCfg.BiasVolt < -1100.0f)
AppIMPCfg.BiasVolt = -1100.0f + DAC12BITVOLT_1LSB;
if (AppIMPCfg.BiasVolt > 1100.0f)
AppIMPCfg.BiasVolt = 1100.0f - DAC12BITVOLT_1LSB;
lpdac_cfg.DacData12Bit =
(uint32_t)((AppIMPCfg.BiasVolt + 1100.0f) / DAC12BITVOLT_1LSB);
lpdac_cfg.DataRst = bFALSE; /* Do not reset data register */
lpdac_cfg.LpDacSW = LPDACSW_VBIAS2LPPA | LPDACSW_VBIAS2PIN |
LPDACSW_VZERO2LPTIA | LPDACSW_VZERO2PIN |
LPDACSW_VZERO2HSTIA;
lpdac_cfg.LpDacRef = LPDACREF_2P5;
lpdac_cfg.LpDacSrc = LPDACSRC_MMR; /* Use MMR data, we use LPDAC to generate
bias voltage for LPTIA - the Vzero */
lpdac_cfg.PowerEn = bTRUE; /* Power up LPDAC */
AD5940_LPDACCfgS(&lpdac_cfg);
}
dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N;
dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P;
dsp_cfg.ADCBaseCfg.ADCPga = AppIMPCfg.AdcPgaGain;
memset(&dsp_cfg.ADCDigCompCfg, 0, sizeof(dsp_cfg.ADCDigCompCfg));
dsp_cfg.ADCFilterCfg.ADCAvgNum = AppIMPCfg.ADCAvgNum;
dsp_cfg.ADCFilterCfg.ADCRate =
ADCRATE_800KHZ; /* Tell filter block clock rate of ADC*/
dsp_cfg.ADCFilterCfg.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
dsp_cfg.ADCFilterCfg.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
dsp_cfg.ADCFilterCfg.BpNotch = bTRUE;
dsp_cfg.ADCFilterCfg.BpSinc3 = bFALSE;
dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE;
dsp_cfg.DftCfg.DftNum = AppIMPCfg.DftNum;
dsp_cfg.DftCfg.DftSrc = AppIMPCfg.DftSrc;
dsp_cfg.DftCfg.HanWinEn = AppIMPCfg.HanWinEn;
memset(&dsp_cfg.StatCfg, 0, sizeof(dsp_cfg.StatCfg));
AD5940_DSPCfgS(&dsp_cfg);
/* Enable all of them. They are automatically turned off during hibernate mode
* to save power */
if (AppIMPCfg.BiasVolt == 0.0f)
AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR |
AFECTRL_WG | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR |
AFECTRL_SINC2NOTCH,
bTRUE);
else
AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR |
AFECTRL_WG | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR |
AFECTRL_SINC2NOTCH | AFECTRL_DCBUFPWR,
bTRUE);
/* Sequence end. */
AD5940_SEQGenInsert(SEQ_STOP()); /* Add one extra command to disable sequencer
for initialization sequence because we
only want it to run one time. */
/* Stop here */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if (error == AD5940ERR_OK) {
AppIMPCfg.InitSeqInfo.SeqId = SEQID_1;
AppIMPCfg.InitSeqInfo.SeqRamAddr = AppIMPCfg.SeqStartAddr;
AppIMPCfg.InitSeqInfo.pSeqCmd = pSeqCmd;
AppIMPCfg.InitSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppIMPCfg.InitSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
} else
return error; /* Error */
return AD5940ERR_OK;
}
static AD5940Err AppIMPSeqMeasureGen(void) {
AD5940Err error = AD5940ERR_OK;
const uint32_t *pSeqCmd;
uint32_t SeqLen;
uint32_t WaitClks;
SWMatrixCfg_Type sw_cfg;
ClksCalInfo_Type clks_cal;
clks_cal.DataType = DATATYPE_DFT;
clks_cal.DftSrc = AppIMPCfg.DftSrc;
clks_cal.DataCount = 1L << (AppIMPCfg.DftNum + 2); /* 2^(DFTNUMBER+2) */
clks_cal.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
clks_cal.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
clks_cal.ADCAvgNum = AppIMPCfg.ADCAvgNum;
clks_cal.RatioSys2AdcClk = AppIMPCfg.SysClkFreq / AppIMPCfg.AdcClkFreq;
AD5940_ClksCalculate(&clks_cal, &WaitClks);
AD5940_SEQGenCtrl(bTRUE);
AD5940_SEQGpioCtrlS(
AGPIO_Pin2); /* Set GPIO1, clear others that under control */
AD5940_SEQGenInsert(SEQ_WAIT(16 * 250)); /* @todo wait 250us? */
sw_cfg.Dswitch = SWD_RCAL0;
sw_cfg.Pswitch = SWP_RCAL0;
sw_cfg.Nswitch = SWN_RCAL1;
sw_cfg.Tswitch = SWT_RCAL1 | SWT_TRTIA;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR |
AFECTRL_WG | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR |
AFECTRL_SINC2NOTCH,
bTRUE);
AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR,
bTRUE); /* Enable Waveform generator */
// delay for signal settling DFT_WAIT
AD5940_SEQGenInsert(SEQ_WAIT(16 * 10));
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT,
bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks));
// wait for first data ready
AD5940_AFECtrlS(AFECTRL_ADCPWR | AFECTRL_ADCCNV | AFECTRL_DFT | AFECTRL_WG,
bFALSE); /* Stop ADC convert and DFT */
/* Configure matrix for external Rz */
sw_cfg.Dswitch = AppIMPCfg.DswitchSel;
sw_cfg.Pswitch = AppIMPCfg.PswitchSel;
sw_cfg.Nswitch = AppIMPCfg.NswitchSel;
sw_cfg.Tswitch = SWT_TRTIA | AppIMPCfg.TswitchSel;
AD5940_SWMatrixCfgS(&sw_cfg);
AD5940_AFECtrlS(AFECTRL_ADCPWR | AFECTRL_WG,
bTRUE); /* Enable Waveform generator */
AD5940_SEQGenInsert(SEQ_WAIT(16 * 10)); // delay for signal settling DFT_WAIT
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT,
bTRUE); /* Start ADC convert and DFT */
AD5940_SEQGenInsert(SEQ_WAIT(WaitClks)); /* wait for first data ready */
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT | AFECTRL_WG | AFECTRL_ADCPWR,
bFALSE); /* Stop ADC convert and DFT */
AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR |
AFECTRL_WG | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR |
AFECTRL_SINC2NOTCH,
bFALSE);
AD5940_SEQGpioCtrlS(0); /* Clr GPIO1 */
AD5940_EnterSleepS(); /* Goto hibernate */
/* Sequence end. */
error = AD5940_SEQGenFetchSeq(&pSeqCmd, &SeqLen);
AD5940_SEQGenCtrl(bFALSE); /* Stop sequencer generator */
if (error == AD5940ERR_OK) {
AppIMPCfg.MeasureSeqInfo.SeqId = SEQID_0;
AppIMPCfg.MeasureSeqInfo.SeqRamAddr =
AppIMPCfg.InitSeqInfo.SeqRamAddr + AppIMPCfg.InitSeqInfo.SeqLen;
AppIMPCfg.MeasureSeqInfo.pSeqCmd = pSeqCmd;
AppIMPCfg.MeasureSeqInfo.SeqLen = SeqLen;
/* Write command to SRAM */
AD5940_SEQCmdWrite(AppIMPCfg.MeasureSeqInfo.SeqRamAddr, pSeqCmd, SeqLen);
} else
return error; /* Error */
return AD5940ERR_OK;
}
AD5940Err AppIMPRtiaCal(void) {
HSRTIACal_Type hs_cal;
fImpPol_Type res;
AD5940Err error;
hs_cal.fFreq = AppIMPCfg.SinFreq;
hs_cal.fRcal = AppIMPCfg.RcalVal;
hs_cal.SysClkFreq = AppIMPCfg.SysClkFreq;
hs_cal.AdcClkFreq = AppIMPCfg.AdcClkFreq;
hs_cal.HsTiaCfg.DiodeClose = bFALSE;
if (AppIMPCfg.BiasVolt != 0.0f)
hs_cal.HsTiaCfg.HstiaBias = HSTIABIAS_VZERO0;
else
hs_cal.HsTiaCfg.HstiaBias = HSTIABIAS_1P1;
hs_cal.HsTiaCfg.HstiaCtia = 31;
hs_cal.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN;
hs_cal.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN;
hs_cal.HsTiaCfg.HstiaRtiaSel = AppIMPCfg.HstiaRtiaSel;
hs_cal.ADCSinc3Osr = AppIMPCfg.ADCSinc3Osr;
hs_cal.ADCSinc2Osr = AppIMPCfg.ADCSinc2Osr;
hs_cal.DftCfg.DftNum = AppIMPCfg.DftNum;
hs_cal.DftCfg.DftSrc = AppIMPCfg.DftSrc;
hs_cal.DftCfg.HanWinEn = AppIMPCfg.HanWinEn;
hs_cal.bPolarResult = bTRUE;
hs_cal.bPolarResult = bTRUE;
error = AD5940_HSRtiaCal(&hs_cal, &res);
if (error == AD5940ERR_OK) {
ResRtiaCal = res.Magnitude;
printf("Calibrated RTIA: %f Ohm, Phase: %f\n", res.Magnitude, res.Phase);
} else
printf("RTIA Calibration Failed: %d\n", error);
return error;
}
/* This function provide application initialize. It can also enable Wupt that
* will automatically trigger sequence. Or it can configure */
int32_t AppIMPInit(uint32_t *pBuffer, uint32_t BufferSize) {
AD5940Err error = AD5940ERR_OK;
SEQCfg_Type seq_cfg;
FIFOCfg_Type fifo_cfg;
if (AD5940_WakeUp(10) >
10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
/* Configure sequencer and stop it */
seq_cfg.SeqMemSize =
SEQMEMSIZE_2KB; /* 2kB SRAM is used for sequencer, others for data FIFO */
seq_cfg.SeqBreakEn = bFALSE;
seq_cfg.SeqIgnoreEn = bTRUE;
seq_cfg.SeqCntCRCClr = bTRUE;
seq_cfg.SeqEnable = bFALSE;
seq_cfg.SeqWrTimer = 0;
AD5940_SEQCfg(&seq_cfg);
/* Reconfigure FIFO */
AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE); /* Disable FIFO firstly */
fifo_cfg.FIFOEn = bTRUE;
fifo_cfg.FIFOMode = FIFOMODE_FIFO;
fifo_cfg.FIFOSize =
FIFOSIZE_4KB; /* 4kB for FIFO, The reset 2kB for sequencer */
fifo_cfg.FIFOSrc = FIFOSRC_DFT;
fifo_cfg.FIFOThresh =
AppIMPCfg
.FifoThresh; /* DFT result. One pair for RCAL, another for Rz. One DFT
result have real part and imaginary part */
AD5940_FIFOCfg(&fifo_cfg);
AD5940_INTCClrFlag(AFEINTSRC_ALLINT);
/* Perform RTIA Calibration */
AppIMPRtiaCal();
/* Start sequence generator */
/* Initialize sequencer generator */
if ((AppIMPCfg.IMPInited == bFALSE) || (AppIMPCfg.bParaChanged == bTRUE)) {
if (pBuffer == 0)
return AD5940ERR_PARA;
if (BufferSize == 0)
return AD5940ERR_PARA;
AD5940_SEQGenInit(pBuffer, BufferSize);
/* Generate initialize sequence */
error = AppIMPSeqCfgGen(); /* Application initialization sequence using
either MCU or sequencer */
if (error != AD5940ERR_OK)
return error;
/* Generate measurement sequence */
error = AppIMPSeqMeasureGen();
if (error != AD5940ERR_OK)
return error;
AppIMPCfg.bParaChanged = bFALSE; /* Clear this flag as we already
implemented the new configuration */
}
/* Initialization sequencer */
AppIMPCfg.InitSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppIMPCfg.InitSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer */
AD5940_SEQMmrTrig(AppIMPCfg.InitSeqInfo.SeqId);
while (AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_ENDSEQ) == bFALSE)
;
/* Measurement sequence */
AppIMPCfg.MeasureSeqInfo.WriteSRAM = bFALSE;
AD5940_SEQInfoCfg(&AppIMPCfg.MeasureSeqInfo);
seq_cfg.SeqEnable = bTRUE;
AD5940_SEQCfg(&seq_cfg); /* Enable sequencer, and wait for trigger */
AD5940_ClrMCUIntFlag(); /* Clear interrupt flag generated before */
AD5940_AFEPwrBW(AppIMPCfg.PwrMod, AFEBW_250KHZ);
AppIMPCfg.IMPInited = bTRUE; /* IMP application has been initialized. */
return AD5940ERR_OK;
}
/* Modify registers when AFE wakeup */
int32_t AppIMPRegModify(int32_t *const pData, uint32_t *pDataCount) {
if (AppIMPCfg.NumOfData > 0) {
AppIMPCfg.FifoDataCount += *pDataCount / 4;
if (AppIMPCfg.FifoDataCount >= AppIMPCfg.NumOfData) {
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
}
if (AppIMPCfg.StopRequired == bTRUE) {
AD5940_WUPTCtrl(bFALSE);
return AD5940ERR_OK;
}
if (AppIMPCfg.SweepCfg
.SweepEn) /* Need to set new frequency and set power mode */
{
AD5940_WGFreqCtrlS(AppIMPCfg.SweepNextFreq, AppIMPCfg.SysClkFreq);
}
return AD5940ERR_OK;
}
/* Depending on the data type, do appropriate data pre-process before return
* back to controller */
int32_t AppIMPDataProcess(int32_t *const pData, uint32_t *pDataCount) {
uint32_t DataCount = *pDataCount;
uint32_t ImpResCount = DataCount / 4;
fImpPol_Type *const pOut = (fImpPol_Type *)pData;
iImpCar_Type *pSrcData = (iImpCar_Type *)pData;
*pDataCount = 0;
DataCount = (DataCount / 4) *
4; /* We expect RCAL data together with Rz data. One DFT result
has two data in FIFO, real part and imaginary part. */
/* Convert DFT result to int32_t type */
for (uint32_t i = 0; i < DataCount; i++) {
pData[i] &= 0x3ffff; /* @todo option to check ECC */
if (pData[i] & (1L << 17)) /* Bit17 is sign bit */
{
pData[i] |= 0xfffc0000; /* Data is 18bit in two's complement, bit17 is the
sign bit */
}
}
for (uint32_t i = 0; i < ImpResCount; i++) {
iImpCar_Type *pDftRcal, *pDftRz;
pDftRcal = pSrcData++;
pDftRz = pSrcData++;
float RzMag, RzPhase;
float RcalMag, RcalPhase;
RcalMag = sqrt((float)pDftRcal->Real * pDftRcal->Real +
(float)pDftRcal->Image * pDftRcal->Image);
RcalPhase = atan2(-pDftRcal->Image, pDftRcal->Real);
RzMag = sqrt((float)pDftRz->Real * pDftRz->Real +
(float)pDftRz->Image * pDftRz->Image);
RzPhase = atan2(-pDftRz->Image, pDftRz->Real);
RzMag = RcalMag / RzMag * AppIMPCfg.RcalVal;
RzPhase = RcalPhase - RzPhase;
// DEBUG: Print raw magnitudes and Rcal used
if (i == 0) {
uint32_t RtiaSel = AppIMPCfg.HstiaRtiaSel;
float ExpectedRtia = (float)HpRtiaTable[RtiaSel];
printf("\n--- RTIA Calibration Info ---\n");
printf("Selected RTIA Index: %d\n", RtiaSel);
printf("Expected RTIA: %.2f Ohm\n", ExpectedRtia);
printf("Calibrated RTIA: %f Ohm\n", ResRtiaCal);
printf("-----------------------------\n");
}
printf("DEBUG: RcalMag: %f, DutMag: %f, RcalVal: %f, RTIA_Cal: %f\n",
RcalMag, (RcalMag * AppIMPCfg.RcalVal) / RzMag, AppIMPCfg.RcalVal,
ResRtiaCal);
pOut[i].Magnitude = RzMag;
pOut[i].Phase = RzPhase;
}
*pDataCount = ImpResCount;
AppIMPCfg.FreqofData = AppIMPCfg.SweepCurrFreq;
/* Calculate next frequency point */
if (AppIMPCfg.SweepCfg.SweepEn == bTRUE) {
AppIMPCfg.FreqofData = AppIMPCfg.SweepCurrFreq;
AppIMPCfg.SweepCurrFreq = AppIMPCfg.SweepNextFreq;
AD5940_SweepNext(&AppIMPCfg.SweepCfg, &AppIMPCfg.SweepNextFreq);
}
return 0;
}
/**
*/
int32_t AppIMPISR(void *pBuff, uint32_t *pCount) {
uint32_t BuffCount;
uint32_t FifoCnt;
BuffCount = *pCount;
*pCount = 0;
if (AD5940_WakeUp(10) >
10) /* Wakeup AFE by read register, read 10 times at most */
return AD5940ERR_WAKEUP; /* Wakeup Failed */
AD5940_SleepKeyCtrlS(SLPKEY_LOCK); /* Prohibit AFE to enter sleep mode. */
if (AD5940_INTCTestFlag(AFEINTC_0, AFEINTSRC_DATAFIFOTHRESH) == bTRUE) {
/* Now there should be 4 data in FIFO */
FifoCnt = (AD5940_FIFOGetCnt() / 4) * 4;
if (FifoCnt > BuffCount) {
///@todo buffer is limited.
}
AD5940_FIFORd((uint32_t *)pBuff, FifoCnt);
AD5940_INTCClrFlag(AFEINTSRC_DATAFIFOTHRESH);
AppIMPRegModify(pBuff,
&FifoCnt); /* If there is need to do AFE re-configure, do it
here when AFE is in active state */
// AD5940_EnterSleepS(); /* Manually put AFE back to hibernate mode. This
// operation only takes effect when register value is ACTIVE previously */
AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); /* Allow AFE to enter sleep mode. */
/* Process data */
AppIMPDataProcess((int32_t *)pBuff, &FifoCnt);
*pCount = FifoCnt;
return 0;
}
return 0;
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,121 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
# disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
else ()
FetchContent_Populate(
pico_sdk
QUIET
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
)
endif ()
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

View File

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

View File

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

View File

@ -1,2 +1,3 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
idf_component_register(SRCS "esp32s3_port.c" "main.c" "webserver.c"
INCLUDE_DIRS "."
REQUIRES ad5940 esp_http_server nvs_flash esp_wifi driver)

99
main/esp32s3_port.c Normal file
View File

@ -0,0 +1,99 @@
/**
* ESP32-S3 port for AD5940 mirrors the RP2040 port as closely as possible.
* Kept minimal: SPI init, GPIO init, ReadWriteNBytes, CS/RST/Delay.
*/
#include "esp32s3_port.h"
#include "ad5940.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_log.h"
#include "esp_rom_sys.h"
#include <string.h>
static spi_device_handle_t spi;
static volatile uint32_t ucInterrupted = 0;
static void IRAM_ATTR gpio_isr_handler(void *arg) { ucInterrupted = 1; }
/**
* Mirrors RP2040 setup_pins():
* spi_init(spi0, 16000000);
* gpio_set_function(MISO/SCK/MOSI, GPIO_FUNC_SPI);
* gpio_init(CS/RST) as output high;
* gpio_init(INT) as input with pull-up;
*/
uint32_t AD5940_MCUResourceInit(void *pCfg) {
// --- SPI at 16 MHz, Mode 0 ---
spi_bus_config_t buscfg = {
.miso_io_num = AD5940_MISO_PIN,
.mosi_io_num = AD5940_MOSI_PIN,
.sclk_io_num = AD5940_SCK_PIN,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 64,
};
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 16 * 1000 * 1000,
.mode = 0,
.spics_io_num = -1, // Manual CS
.queue_size = 1,
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_DISABLED));
ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg, &spi));
// --- CS pin: output, high ---
gpio_reset_pin(AD5940_CS_PIN);
gpio_set_direction(AD5940_CS_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(AD5940_CS_PIN, 1);
// --- RST pin: output, high ---
gpio_reset_pin(AD5940_RST_PIN);
gpio_set_direction(AD5940_RST_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(AD5940_RST_PIN, 1);
// --- INT pin: input, pull-up ---
gpio_reset_pin(AD5940_GP0INT_PIN);
gpio_set_direction(AD5940_GP0INT_PIN, GPIO_MODE_INPUT);
gpio_pullup_en(AD5940_GP0INT_PIN);
gpio_install_isr_service(0);
gpio_set_intr_type(AD5940_GP0INT_PIN, GPIO_INTR_NEGEDGE);
gpio_isr_handler_add(AD5940_GP0INT_PIN, gpio_isr_handler,
(void *)AD5940_GP0INT_PIN);
return 0;
}
// --- Platform Interface (same signatures as RP2040 port) ---
void AD5940_CsClr(void) { gpio_set_level(AD5940_CS_PIN, 0); }
void AD5940_CsSet(void) { gpio_set_level(AD5940_CS_PIN, 1); }
void AD5940_RstClr(void) { gpio_set_level(AD5940_RST_PIN, 0); }
void AD5940_RstSet(void) { gpio_set_level(AD5940_RST_PIN, 1); }
void AD5940_Delay10us(uint32_t time) { esp_rom_delay_us(time * 10); }
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer,
unsigned char *pRecvBuff, unsigned long length) {
spi_transaction_t t = {
.length = length * 8,
.tx_buffer = pSendBuffer,
.rx_buffer = pRecvBuff,
};
spi_device_polling_transmit(spi, &t);
}
uint32_t AD5940_GetMCUIntFlag(void) { return ucInterrupted; }
uint32_t AD5940_ClrMCUIntFlag(void) {
ucInterrupted = 0;
return 1;
}
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;
}

27
main/esp32s3_port.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef _AD5940_PORT_H_
#define _AD5940_PORT_H_
#include "sdkconfig.h"
#include <stdint.h>
// Pin Definitions for ESP32-S3-Zero
#define AD5940_MISO_PIN 1
#define AD5940_CS_PIN 2
#define AD5940_SCK_PIN 4
#define AD5940_MOSI_PIN 5
#define AD5940_GP0INT_PIN 6
#define AD5940_RST_PIN 7
// Function Prototypes
uint32_t AD5940_MCUResourceInit(void *pCfg);
void AD5940_CsClr(void);
void AD5940_CsSet(void);
void AD5940_RstSet(void);
void AD5940_RstClr(void);
void AD5940_Delay10us(uint32_t time);
void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer,
unsigned char *pRecvBuff, unsigned long length);
uint32_t AD5940_GetMCUIntFlag(void);
uint32_t AD5940_ClrMCUIntFlag(void);
#endif // _AD5940_PORT_H_

View File

@ -1,6 +1,67 @@
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include <stdio.h>
#include <string.h>
void app_main(void)
{
#include "ad5940.h"
#include "esp32s3_port.h"
#include "webserver.h"
#define TAG "main"
void app_main(void) {
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize WiFi and Webserver
ESP_LOGI(TAG, "Initializing WiFi...");
wifi_init_sta();
// start_webserver() is now called from IP_EVENT_STA_GOT_IP handler
// Initialize AD5940 Platform (SPI + GPIOs)
ESP_LOGI(TAG, "Initializing AD5940 Platform...");
AD5940_MCUResourceInit(0);
// Hard-reset the AD5940
ESP_LOGI(TAG, "AD5940_HWReset...");
AD5940_HWReset();
AD5940_Delay10us(25000); // 250ms for LDOs
// Initialize AD5940 registers
ESP_LOGI(TAG, "AD5940_Initialize...");
AD5940_Initialize();
// Initial log
web_log("System Started.\nWaiting for commands...\n");
while (1) {
if (check_id_requested()) {
web_log("Executing Check ID...\n");
// Wakeup AFE (Pulse CS)
AD5940_CsClr();
AD5940_Delay10us(10); // 100us pulse
AD5940_CsSet();
AD5940_Delay10us(100); // Wait 1ms for wake
// Read Chip ID
// Using direct register read if possible, or driver function
// Assuming AD5940_ReadReg is available in ad5940.c
uint32_t chip_id = AD5940_ReadReg(REG_AFECON_CHIPID);
web_log("CHIP ID: 0x%04X\n", chip_id);
ESP_LOGI(TAG, "CHIP ID: 0x%04X", chip_id);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}

245
main/webserver.c Normal file
View File

@ -0,0 +1,245 @@
#include "webserver.h"
#include "esp_event.h"
#include "esp_http_server.h"
#include "esp_log.h"
#include "esp_mac.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#define AP_WIFI_SSID "AutoSpa"
#define AP_WIFI_PASS "autospa123"
#define AP_MAX_CONN 4
#define AP_WIFI_CHANNEL 1
#define STA_WIFI_SSID "Big_Blue_House"
#define STA_WIFI_PASS "tDMiar*2024"
static const char *TAG = "webserver";
static volatile int s_check_id_req = 0;
// Log buffer
#define LOG_BUF_SIZE 4096
static char log_buffer[LOG_BUF_SIZE];
static int log_head = 0;
void web_log(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
char temp[256];
int len = vsnprintf(temp, sizeof(temp), fmt, args);
va_end(args);
if (len > 0) {
if (log_head + len < LOG_BUF_SIZE - 1) {
memcpy(log_buffer + log_head, temp, len);
log_head += len;
log_buffer[log_head] = '\0';
} else {
log_head = 0;
memcpy(log_buffer + log_head, temp, len);
log_head += len;
log_buffer[log_head] = '\0';
}
ESP_LOGI(TAG, "%s", temp);
}
}
int check_id_requested(void) {
if (s_check_id_req) {
s_check_id_req = 0;
return 1;
}
return 0;
}
/* WiFi AP Event Handler */
/* WiFi Event Handler */
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t *event =
(wifi_event_ap_staconnected_t *)event_data;
ESP_LOGI(TAG, "station " MACSTR " joined, AID=%d", MAC2STR(event->mac),
event->aid);
web_log("Client connected!\n");
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t *event =
(wifi_event_ap_stadisconnected_t *)event_data;
ESP_LOGI(TAG, "station " MACSTR " left, AID=%d", MAC2STR(event->mac),
event->aid);
web_log("Client disconnected.\n");
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect();
ESP_LOGI(TAG, "retry to connect to the AP");
web_log("Retry to connect to the AP\n");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
web_log("Got IP: " IPSTR "\n", IP2STR(&event->ip_info.ip));
start_webserver();
}
}
void wifi_init_ap(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
wifi_config_t wifi_config = {
.ap =
{
.ssid = AP_WIFI_SSID,
.ssid_len = strlen(AP_WIFI_SSID),
.channel = AP_WIFI_CHANNEL,
.password = AP_WIFI_PASS,
.max_connection = AP_MAX_CONN,
.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {.required = true},
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "SoftAP started. SSID: %s, Password: %s", AP_WIFI_SSID,
AP_WIFI_PASS);
ESP_LOGI(TAG, "Connect to this WiFi and browse to http://192.168.4.1:9099");
}
void wifi_init_sta(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, NULL));
wifi_config_t wifi_config = {
.sta =
{
.ssid = STA_WIFI_SSID,
.password = STA_WIFI_PASS,
/* Setting a password implies station will connect to all security
* modes including WEP/WPA. However these modes are deprecated and
* not advised to be used. Incase your Access point doesn't
* support WPA2, these mode can be enabled by commenting below
* line */
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished.");
}
/* HTTP Server Handlers */
static const char *index_html =
"<!DOCTYPE html><html><head><title>AD5940 Control</title></head><body>"
"<h1>AD5940 ESP32-S3 Dashboard</h1>"
"<button onclick=\"sendCommand('check_id')\">Check ID</button>"
"<h2>Console Log</h2>"
"<pre id=\"console\" "
"style=\"background:#eee;padding:10px;height:300px;overflow:auto;\"></pre>"
"<script>"
"function sendCommand(cmd) {"
" fetch('/api/command', {method:'POST', "
"body:JSON.stringify({command:cmd})});"
"}"
"setInterval(() => {"
" fetch('/api/log').then(r => r.text()).then(t => {"
" if(t) document.getElementById('console').textContent = t;"
" });"
"}, 1000);"
"</script></body></html>";
static esp_err_t root_get_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Request received for %s", req->uri);
httpd_resp_send(req, index_html, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
static esp_err_t command_post_handler(httpd_req_t *req) {
char buf[100];
int ret, remaining = req->content_len;
if (remaining >= sizeof(buf)) {
remaining = sizeof(buf) - 1;
}
ret = httpd_req_recv(req, buf, remaining);
if (ret <= 0)
return ESP_FAIL;
buf[ret] = '\0';
if (strstr(buf, "check_id")) {
s_check_id_req = 1;
web_log("Command received: Check ID\n");
}
httpd_resp_send(req, "OK", HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
static esp_err_t log_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "text/plain");
httpd_resp_send(req, log_buffer, HTTPD_RESP_USE_STRLEN);
// Note: This sends the whole buffer every time.
// Optimization: implement cursor or clear on read.
// For now, this is enough for "Check ID" simple verification.
return ESP_OK;
}
void start_webserver(void) {
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.server_port = 9099;
config.lru_purge_enable = true;
ESP_LOGI(TAG, "Starting web server"); // Log to serial for debug too
if (httpd_start(&server, &config) == ESP_OK) {
ESP_LOGI(TAG, "Web server started on port %d", config.server_port);
httpd_uri_t root = {.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler,
.user_ctx = NULL};
httpd_register_uri_handler(server, &root);
httpd_uri_t cmd = {.uri = "/api/command",
.method = HTTP_POST,
.handler = command_post_handler,
.user_ctx = NULL};
httpd_register_uri_handler(server, &cmd);
httpd_uri_t log_uri = {.uri = "/api/log",
.method = HTTP_GET,
.handler = log_get_handler,
.user_ctx = NULL};
httpd_register_uri_handler(server, &log_uri);
} else {
ESP_LOGE(TAG, "Error starting server!");
}
}

12
main/webserver.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef _WEBSERVER_H_
#define _WEBSERVER_H_
#include <stdarg.h>
void start_webserver(void);
void wifi_init_ap(void);
void wifi_init_sta(void);
void web_log(const char *fmt, ...);
int check_id_requested(void); // Returns 1 if requested, resets flag
#endif