commit 9eff1ecd367a23a2c680f0fc2f4a751c0c7e50ae Author: pszsh Date: Thu Jan 22 03:43:44 2026 -0800 EIS diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a8e89e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +.vscode +Packed +*.py +venv +build +*.swp +*.cmake +requirements.txt +ad5940* +pico_sdk_import.cmake +build* +*.png +icons/ +*.pdf + +build/ +build_android/ +build_ios/ +build_macos/ +build_windows/ +icons +*.png \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7064e15 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +# File: CMakeLists.txt + +cmake_minimum_required(VERSION 3.13) + +include(pico_sdk_import.cmake) + +project(EIS C CXX ASM) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +pico_sdk_init() + +# Add ad5940.c to the build +add_executable(EIS + main.c + ad5940.c +) + +# Define CHIPSEL_594X for the library +target_compile_definitions(EIS PRIVATE CHIPSEL_594X) + +# REQUIRED: Tell CMake where to find headers +target_include_directories(EIS PRIVATE ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(EIS + 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) \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a591a17 --- /dev/null +++ b/Makefile @@ -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 diff --git a/drv.min.h b/drv.min.h new file mode 100644 index 0000000..09aa109 --- /dev/null +++ b/drv.min.h @@ -0,0 +1,262 @@ +/* Library Version */ +AD5940LIB_VER_MAJOR, AD5940LIB_VER_MINOR, AD5940LIB_VER_PATCH, AD5940LIB_VER + +/* AGPIO Registers (0x00) */ +REG_AGPIO_GP0CON, REG_AGPIO_GP0OEN, REG_AGPIO_GP0PE, REG_AGPIO_GP0IEN +REG_AGPIO_GP0IN, REG_AGPIO_GP0OUT, REG_AGPIO_GP0SET, REG_AGPIO_GP0CLR, REG_AGPIO_GP0TGL + +/* AGPIO Bitmasks */ +BITP/M_AGPIO_GP0CON_PINxCFG (x=0-7) +BITP/M_AGPIO_GP0OEN_OEN, BITP/M_AGPIO_GP0PE_PE, BITP/M_AGPIO_GP0IEN_IEN +BITP/M_AGPIO_GP0IN_IN, BITP/M_AGPIO_GP0OUT_OUT, BITP/M_AGPIO_GP0SET_SET +BITP/M_AGPIO_GP0CLR_CLR, BITP/M_AGPIO_GP0TGL_TGL + +/* AFECON Registers (0x400) */ +REG_AFECON_ADIID, REG_AFECON_CHIPID, REG_AFECON_CLKCON0, REG_AFECON_CLKEN1 +REG_AFECON_CLKSEL, REG_AFECON_CLKCON0KEY, REG_AFECON_SWRSTCON, REG_AFECON_TRIGSEQ + +/* AFECON Bitmasks */ +BITP/M_AFECON_ADIID_ADIID, BITP/M_AFECON_CHIPID_PARTID/REVISION +BITP/M_AFECON_CLKCON0_SFFTCLKDIVCNT, ADCCLKDIV, SYSCLKDIV +BITP/M_AFECON_CLKEN1_GPT1DIS, GPT0DIS, ACLKDIS +BITP/M_AFECON_CLKSEL_ADCCLKSEL, SYSCLKSEL +BITP/M_AFECON_CLKCON0KEY_DIVSYSCLK_ULP_EN +BITP/M_AFECON_SWRSTCON_SWRSTL +BITP/M_AFECON_TRIGSEQ_TRIGx (x=0-3) + +/* AFEWDT Registers (0x900) */ +REG_AFEWDT_WDTLD, REG_AFEWDT_WDTVALS, REG_AFEWDT_WDTCON, REG_AFEWDT_WDTCLRI +REG_AFEWDT_WDTSTA, REG_AFEWDT_WDTMINLD + +/* AFEWDT Bitmasks */ +BITP/M_AFEWDT_WDTLD_LOAD, BITP/M_AFEWDT_WDTVALS_CCOUNT +BITP/M_AFEWDT_WDTCON_WDTIRQEN, MINLOAD_EN, CLKDIV2, MDE, EN, PRE, IRQ, PDSTOP +ENUM_AFEWDT_WDTCON_RESET, INTERRUPT, CONTINUE, STOP +BITP/M_AFEWDT_WDTCLRI_CLRWDG +BITP/M_AFEWDT_WDTSTA_TMINLD, OTPWRDONE, LOCK, CON, TLD, CLRI, IRQ +ENUM_AFEWDT_WDTSTA_OPEN, LOCKED, SYNC_COMPLETE, SYNC_IN_PROGRESS, CLEARED, PENDING +BITP/M_AFEWDT_WDTMINLD_MIN_LOAD + +/* WUPTMR Registers (0x800) */ +REG_WUPTMR_CON, REG_WUPTMR_SEQORDER +REG_WUPTMR_SEQxWUPL/H (x=0-3), REG_WUPTMR_SEQxSLEEPL/H (x=0-3) + +/* WUPTMR Bitmasks */ +BITP/M_WUPTMR_CON_MSKTRG, CLKSEL, ENDSEQ, EN +ENUM_WUPTMR_CON_SWT32K0, SWTEXT0, SWT32K, SWTEXT, SWTEN, SWTDIS +ENUM_WUPTMR_CON_ENDSEQx (x=A-H) +BITP/M_WUPTMR_SEQORDER_SEQx (x=A-H) +ENUM_WUPTMR_SEQORDER_SEQx0-3 +BITP/M_WUPTMR_SEQxWUPL/H_WAKEUPTIMEx, BITP/M_WUPTMR_SEQxSLEEPL/H_SLEEPTIMEx + +/* ALLON Registers (0xA00) */ +REG_ALLON_PWRMOD, REG_ALLON_PWRKEY, REG_ALLON_OSCKEY, REG_ALLON_OSCCON +REG_ALLON_TMRCON, REG_ALLON_EI0CON, REG_ALLON_EI1CON, REG_ALLON_EI2CON +REG_ALLON_EICLR, REG_ALLON_RSTSTA, REG_ALLON_RSTCONKEY, REG_ALLON_LOSCTST, REG_ALLON_CLKEN0 + +/* ALLON Bitmasks */ +BITP/M_ALLON_PWRMOD_RAMRETEN, ADCRETEN, SEQSLPEN, TMRSLPEN, PWRMOD +BITP/M_ALLON_PWRKEY_PWRKEY, BITP/M_ALLON_OSCKEY_OSCKEY +BITP/M_ALLON_OSCCON_HFXTALOK, HFOSCOK, LFOSCOK, HFXTALEN, HFOSCEN, LFOSCEN +BITP/M_ALLON_TMRCON_TMRINTEN +BITP/M_ALLON_EI0CON_IRQxEN/MDE (x=0-3) +BITP/M_ALLON_EI1CON_IRQxEN/MDE (x=4-7) +BITP/M_ALLON_EI2CON_BUSINTEN, BUSINTMDE +BITP/M_ALLON_EICLR_AUTCLRBUSEN, BUSINT +BITP/M_ALLON_RSTSTA_PINSWRST, MMRSWRST, WDRST, EXTRST, POR +BITP/M_ALLON_RSTCONKEY_KEY +BITP/M_ALLON_LOSCTST_TRIM +BITP/M_ALLON_CLKEN0_TIACHPDIS, SLPWUTDIS, WDTDIS + +/* AGPT0 Registers (0xD00) & AGPT1 Registers (0xE00) */ +REG_AGPT0_LD0, REG_AGPT0_VAL0, REG_AGPT0_CON0, REG_AGPT0_CLRI0, REG_AGPT0_CAP0 +REG_AGPT0_ALD0, REG_AGPT0_AVAL0, REG_AGPT0_STA0, REG_AGPT0_PWMCON0, REG_AGPT0_PWMMAT0, REG_AGPT0_INTEN +REG_AGPT1_LD1, REG_AGPT1_VAL1, REG_AGPT1_CON1, REG_AGPT1_CLRI1, REG_AGPT1_CAP1 +REG_AGPT1_ALD1, REG_AGPT1_AVAL1, REG_AGPT1_STA1, REG_AGPT1_PWMCON1, REG_AGPT1_PWMMAT1, REG_AGPT1_INTEN1 + +/* AGPT Bitmasks */ +BITP/M_AGPTx_CONx_SYNCBYP, RSTEN, EVTEN, EVENT, RLD, CLK, ENABLE, MOD, UP, PRE +BITP/M_AGPTx_CLRIx_CAP, TMOUT +BITP/M_AGPTx_STAx_RSTCNT, PDOK, BUSY, CAP, TMOUT +BITP/M_AGPTx_PWMCONx_IDLE, MATCHEN + +/* AFECRC Registers (0x1000) */ +REG_AFECRC_CTL, REG_AFECRC_IPDATA, REG_AFECRC_RESULT, REG_AFECRC_POLY +REG_AFECRC_IPBITS, REG_AFECRC_IPBYTE, REG_AFECRC_CRC_SIG_COMP, REG_AFECRC_CRCINTEN, REG_AFECRC_INTSTA + +/* AFECRC Bitmasks */ +BITP/M_AFECRC_CTL_REVID, MON_EN, W16SWP, BYTMIRR, BITMIRR, LSBFIRST, EN + +/* AFE Registers (0x2000) */ +REG_AFE_AFECON, REG_AFE_SEQCON, REG_AFE_FIFOCON, REG_AFE_SWCON, REG_AFE_HSDACCON +REG_AFE_WGCON, REG_AFE_WGDCLEVEL1/2, REG_AFE_WGDELAY1/2, REG_AFE_WGSLOPE1/2 +REG_AFE_WGFCW, REG_AFE_WGPHASE, REG_AFE_WGOFFSET, REG_AFE_WGAMPLITUDE +REG_AFE_ADCFILTERCON, REG_AFE_HSDACDAT, REG_AFE_LPREFBUFCON, REG_AFE_SYNCEXTDEVICE +REG_AFE_SEQCRC, REG_AFE_SEQCNT, REG_AFE_SEQTIMEOUT, REG_AFE_DATAFIFORD, REG_AFE_CMDFIFOWRITE +REG_AFE_ADCDAT, REG_AFE_DFTREAL, REG_AFE_DFTIMAG, REG_AFE_SINC2DAT, REG_AFE_TEMPSENSDAT +REG_AFE_AFEGENINTSTA, REG_AFE_ADCMIN/SM, REG_AFE_ADCMAX/SMEN, REG_AFE_ADCDELTA +REG_AFE_HPOSCCON, REG_AFE_DFTCON +REG_AFE_LPTIASW0/1, REG_AFE_LPTIACON0/1 +REG_AFE_HSRTIACON, REG_AFE_DE0/1RESCON, REG_AFE_HSTIACON +REG_AFE_LPMODEKEY, REG_AFE_LPMODECLKSEL, REG_AFE_LPMODECON +REG_AFE_SEQSLPLOCK, REG_AFE_SEQTRGSLP +REG_AFE_LPDACDAT0/1, REG_AFE_LPDACSW0/1, REG_AFE_LPDACCON0/1 +REG_AFE_DSWFULLCON, REG_AFE_NSWFULLCON, REG_AFE_PSWFULLCON, REG_AFE_TSWFULLCON +REG_AFE_TEMPSENS, REG_AFE_BUFSENCON, REG_AFE_ADCCON +REG_AFE_DSWSTA, REG_AFE_PSWSTA, REG_AFE_NSWSTA, REG_AFE_TSWSTA +REG_AFE_STATSVAR, REG_AFE_STATSCON, REG_AFE_STATSMEAN +REG_AFE_SEQ0/1/2/3INFO, REG_AFE_CMDFIFOWADDR, REG_AFE_CMDDATACON, REG_AFE_DATAFIFOTHRES +REG_AFE_REPEATADCCNV, REG_AFE_FIFOCNTSTA, REG_AFE_CALDATLOCK +REG_AFE_ADCOFFSETHSTIA, REG_AFE_ADCGAINTEMPSENS0, REG_AFE_ADCOFFSETTEMPSENS0/1 +REG_AFE_ADCGAINGN1/1P5/2/4/9, REG_AFE_ADCOFFSETGN1/1P5/2/4/9 +REG_AFE_DACGAIN, REG_AFE_DACOFFSET, REG_AFE_DACOFFSETATTEN/HP +REG_AFE_ADCPGAOFFSETCANCEL, REG_AFE_ADCGNHSTIA, REG_AFE_ADCPGAGN4OFCAL +REG_AFE_ADCOFFSETLPTIA0/1, REG_AFE_ADCGNLPTIA0/1, REG_AFE_ADCGAINDIOTEMPSENS +REG_AFE_PMBW, REG_AFE_SWMUX, REG_AFE_AFE_TEMPSEN_DIO, REG_AFE_ADCBUFCON + +/* AFE Bitmasks & Enums */ +BITP/M_AFE_AFECON_DACBUFEN, DACREFEN, ALDOILIMITEN, SINC2EN, DFTEN, WAVEGENEN +BITP/M_AFE_AFECON_TEMPCONVEN, TEMPSENSEN, TIAEN, INAMPEN, EXBUFEN, ADCCONVEN, ADCEN, DACEN, HPREFDIS +ENUM_AFE_AFECON_OFF, ENUM_AFE_AFECON_ON +BITP/M_AFE_SEQCON_SEQWRTMR, SEQHALT, SEQHALTFIFOEMPTY, SEQEN +BITP/M_AFE_FIFOCON_DATAFIFOSRCSEL, DATAFIFOEN +BITP/M_AFE_SWCON_TxCON, SWSOURCESEL, TMUXCON, NMUXCON, PMUXCON, DMUXCON +BITP/M_AFE_HSDACCON_INAMPGNMDE, RATE, ATTENEN +BITP/M_AFE_WGCON_DACGAINCAL, DACOFFSETCAL, TYPESEL, TRAPRSTEN +BITP/M_AFE_ADCFILTERCON_AVRGNUM, SINC3OSR, SINC2OSR, AVRGEN, SINC3BYP, LPFBYPEN, ADCCLK +BITP/M_AFE_LPREFBUFCON_BOOSTCURRENT, LPBUF2P5DIS, LPREFDIS +BITP/M_AFE_CMDFIFOWRITE_CMDFIFOIN +BITP/M_AFE_CMDDATACON_DATAMEMMDE, DATA_MEM_SEL, CMDMEMMDE, CMD_MEM_SEL +ENUM_AFE_CMDDATACON_DFIFO, DSTM, DMEM32B, DMEM2K, DMEM4K, DMEM6K, CMEM, CFIFO, CSTM, CMEM32B, CMEM2K, CMEM4K, CMEM6K +BITP/M_AFE_DFTCON_DFTINSEL, DFTNUM, HANNINGEN +BITP/M_AFE_LPTIASWx_TIABIASSEL, PABIASSEL, TIASWCON +ENUM_AFE_LPTIASWx_CAPA_LP, NORM, DIO, SHORTSW, LOWNOISE, CAPA_RAMP_H, BUFDIS, BUFEN/2, TWOLEAD, SESHORTRE +BITP/M_AFE_LPTIACONx_CHOPEN, TIARF, TIARL, TIAGAIN, IBOOST, HALFPWR, PAPDEN, TIAPDEN +ENUM_AFE_LPTIACONx_DISCONRF, BYPRF, RF20K, RF100K, RF200K, RF400K, RF600K, RF1MOHM +ENUM_AFE_LPTIACONx_RL0, RL10, RL30, RL50, RL100, RL1P6K, RL3P1K, RL3P5K +ENUM_AFE_LPTIACONx_DISCONTIA, TIAGAIN200, 1K, 2K...512K +BITP/M_AFE_HSRTIACON_CTIACON, TIASW6CON, RTIACON +BITP/M_AFE_DExRESCON_DExRCON +BITP/M_AFE_HSTIACON_VBIASSEL +BITP/M_AFE_DACDCBUFCON_CHANSEL, CHAN0, CHAN1 +BITP/M_AFE_LPMODECON_ALDOEN, V1P1HPADCEN, V1P8HPADCEN, PTATEN, ZTATEN, REPEATADCCNVEN_P, ADCCONVEN, HPREFDIS, HFOSCPD +BITP/M_AFE_LPDACSWx_LPMODEDIS, LPDACSW, DACCONBIT5, OVRRIDE +BITP/M_AFE_LPDACCONx_WAVETYPE, DACMDE, VZEROMUX, VBIASMUX, REFSEL, PWDEN, RSTEN +ENUM_AFE_LPDACCONx_MMR, WAVEGEN, NORM, DIAG, BITS6, BITS12, 12BIT, EN, ULPREF, AVDD, PWREN, PWRDIS, WRITEDIS, WRITEEN +BITP/M_AFE_DSWFULLCON_Dx (x=2-8), DR0 +BITP/M_AFE_NSWFULLCON_NL2, NL, NR1, Nx (x=1-9) +BITP/M_AFE_PSWFULLCON_PL2, PL, Px (x=2-12), PR0 +BITP/M_AFE_TSWFULLCON_TR1, Tx (x=1-11) +BITP/M_AFE_TEMPSENS_CHOPFRESEL, CHOPCON, ENABLE +BITP/M_AFE_BUFSENCON_V1P8THERMSTEN, V1P1LPADCCHGDIS, V1P1LPADCEN, V1P1HPADCEN, V1P8HPADCCHGDIS, V1P8LPADCEN, V1P8HPADCILIMITEN, V1P8HPADCEN +BITP/M_AFE_ADCCON_GNPGA, GNOFSELPGA, GNOFFSEL, MUXSELN, MUXSELP +BITP/M_AFE_STATSCON_STDDEV, SAMPLENUM, RESRVED, STATSEN +BITP/M_AFE_PMBW_SYSBW, SYSHP, BWNA, BW50, BW100, BW250, LP, HP + +/* INTC Registers (0x3000) */ +REG_INTC_INTCPOL, REG_INTC_INTCCLR, REG_INTC_INTCSEL0/1, REG_INTC_INTCFLAG0/1 + +/* SPI Commands */ +SPICMD_SETADDR, SPICMD_READREG, SPICMD_WRITEREG, SPICMD_READFIFO + +/* Interrupt Controller Constants */ +AFEINTC_0, AFEINTC_1 +AFEINTSRC_ADCRDY, DFTRDY, SINC2RDY, TEMPRDY, ADCMINERR, ADCMAXERR, ADCDIFFERR, MEANRDY, VARRDY +AFEINTSRC_CUSTOMINTx (x=0-3), BOOTLDDONE, WAKEUP, ENDSEQ, SEQTIMEOUT, SEQTIMEOUTERR +AFEINTSRC_CMDFIFOFULL, CMDFIFOEMPTY, CMDFIFOTHRESH, CMDFIFOOF, CMDFIFOUF +AFEINTSRC_DATAFIFOFULL, DATAFIFOEMPTY, DATAFIFOTHRESH, DATAFIFOOF, DATAFIFOUF +AFEINTSRC_WDTIRQ, CRC_OUTLIER, GPT0INT_SLPWUT, GPT1INT_TRYBRK, ALLINT + +/* Power & Bandwidth */ +AFEPWR_LP, AFEPWR_HP +AFEBW_AUTOSET, AFEBW_50KHZ, AFEBW_100KHZ, AFEBW_250KHZ + +/* AFE Control Signals (Masks) */ +AFECTRL_HPREFPWR, HSDACPWR, ADCPWR, ADCCNV, EXTBUFPWR, INAMPPWR, HSTIAPWR +AFECTRL_TEMPSPWR, TEMPCNV, WG, DFT, SINC2NOTCH, ALDOLIMIT, DACREFPWR, DCBUFPWR, ALL + +/* Low Power Mode Control Signals */ +LPMODECTRL_HFOSCEN, HPREFPWR, ADCCNV, REPEATEN, GLBBIASZ, GLBBIASP, BUFHP1P8V, BUFHP1P1V, ALDOPWR, ALL, NONE + +/* Result Types */ +AFERESULT_SINC3, SINC2, TEMPSENSOR, DFTREAL, DFTIMAGE, STATSMEAN, STATSVAR + +/* Switch Matrix Definitions */ +SWD_OPEN, RCAL0, AIN1, AIN2, AIN3, CE0, CE1, AFE1, SE0, SE1, AFE3 +SWP_OPEN, RCAL0, AIN1, AIN2, AIN3, RE0, RE1, AFE2, SE0, DE0, SE1, AFE3, DE1, CE0, CE1, AFE1, PL, PL2 +SWN_OPEN, RCAL1, AIN0, AIN1, AIN2, AIN3, SE0LOAD, DE0LOAD, SE1LOAD, AFE3LOAD, DE1LOAD, SE0, NL, NL2 +SWT_OPEN, RCAL1, AIN0, AIN1, AIN2, AIN3, SE0LOAD, DE0, SE1LOAD, AFE3LOAD, DE1, TRTIA, DE0LOAD, DE1LOAD + +/* Waveform Generator Types */ +WGTYPE_MMR, WGTYPE_SIN, WGTYPE_TRAPZ + +/* HSDAC Constants */ +EXCITBUFGAIN_2, EXCITBUFGAIN_0P25 +HSDACGAIN_1, HSDACGAIN_0P2 + +/* HSTIA Constants */ +HSTIABIAS_1P1, VZERO0, VZERO1 +HSTIARTIA_200, 1K, 5K, 10K, 20K, 40K, 80K, 160K, OPEN +HSTIADERTIA_50, 100, 200, 1K, 5K, 10K, 20K, 40K, 80K, 160K, TODE, OPEN +HSTIADERLOAD_0R, 10R, 30R, 50R, 100R, OPEN +HSTIAPWRMOE_LP, HP + +/* LPDAC Constants */ +LPDAC0, LPDAC1 +LPDACSRC_MMR, WG +LPDACSW_VBIAS2LPPA, VBIAS2PIN, VZERO2LPTIA, VZERO2PIN, VZERO2HSTIA +LPDACVZERO_6BIT, 12BIT +LPDACVBIAS_6BIT, 12BIT +LPDACREF_2P5, AVDD + +/* LPAMP/LPTIA Constants */ +LPTIA0, LPTIA1 +LPTIARF_OPEN, SHORT, 20K, 100K, 200K, 400K, 600K, 1M +LPTIARLOAD_SHORT, 10R, 30R, 50R, 100R, 1K6, 3K1, 3K6 +LPTIARTIA_OPEN, 200R, 1K...512K +LPAMP0, LPAMP1 +LPAMPPWR_NORM, BOOST1, BOOST2, BOOST3, HALF +LPTIASW(n) + +/* ADC Constants */ +ADCPGA_1, 1P5, 2, 4, 9 +ADCMUXP_FLOAT, HSTIA_P, AIN0-6, AVDD_2, DVDD_2, AVDDREG, TEMPP/N, VSET1P1, VDE0, VSE0/1, VAFE1-4, VREF2P5, VREF1P8DAC, VZERO0/1, VBIAS0/1, VCE0/1, VRE0/1, VCE0_2, VCE1_2, LPTIA0_P, LPTIA1_P, AGND, P_NODE, IOVDD_2 +ADCMUXN_FLOAT, HSTIA_N, LPTIA0_N, LPTIA1_N, AIN0-6, VSET1P1, VREF1P1, TEMPN, VZERO0/1, VBIAS0/1, AFE4, N_NODE +ADCRATE_800KHZ, 1P6MHZ +ADCSINC3OSR_2, 4, 5 +ADCSINC2OSR_22, 44...1333 +ADCAVGNUM_2, 4, 8, 16 + +/* DFT & Statistics Constants */ +DFTSRC_SINC2NOTCH, SINC3, ADCRAW, AVG +DFTNUM_4, 8...16384 +STATSAMPLE_128, 64, 32, 16, 8 +STATDEV_1, 4, 9, 16, 25 + +/* Sequencer & FIFO Constants */ +SEQID_0, 1, 2, 3 +SEQMEMSIZE_32B, 2KB, 4KB, 6KB +SEQPINTRIGMODE_RISING, FALLING, BOTHEDGE, HIGHL, LOWL +SEQ_WAIT(ClkNum), SEQ_TOUT(ClkNum), SEQ_WR(addr,data) +SEQ_NOP(), SEQ_HALT(), SEQ_STOP(), SEQ_SLP(), SEQ_INTx(), SEQ_LEN(n) +FIFOMODE_FIFO, STREAM +FIFOSRC_SINC3, DFT, SINC2NOTCH, VAR, MEAN +FIFO_SEQID(data), ECC, CHANID +FIFOSIZE_32B, 2KB, 4KB, 6KB +WUPTENDSEQ_A - H + +/* Misc Constants */ +DATATYPE_ADCRAW, SINC3, SINC2, DFT, NOTCH +SLPKEY_LOCK, UNLOCK +HPOSCOUT_32MHZ, 16MHZ +AGPIO_Pin0 - Pin7 +GPx_INT/TRIG/SYNC/GPIO/SLEEP/PORB/EXTCLK +LPMODECLK_HFOSC, LFOSC +SYSCLKSRC_HFOSC, XTAL, LFOSC, EXT +ADCCLKSRC_HFOSC, XTAL, EXT +ADCCLKDIV_1, 2 +SYSCLKDIV_1, 2 +PGACALTYPE_OFFSET, GAIN, OFFSETGAIN +AD5940ERR_OK, ERROR, PARA, NULLP, BUFF, ADDROR, SEQGEN, SEQREG, SEQLEN, WAKEUP, TIMEOUT, CALOR, APPERROR +MATH_PI, AD5940_ADIID, AD5940_CHIPID, M355_ADIID, M355_CHIPID, AD5940_SWRST +KEY_OSCCON, KEY_CALDATLOCK, KEY_LPMODEKEY \ No newline at end of file diff --git a/host/.gitignore b/host/.gitignore new file mode 100644 index 0000000..cc66a45 --- /dev/null +++ b/host/.gitignore @@ -0,0 +1,7 @@ +build/ +build_android/ +build_ios/ +build_macos/ +build_windows/ +icons +*.png \ No newline at end of file diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt new file mode 100644 index 0000000..4c63d28 --- /dev/null +++ b/host/CMakeLists.txt @@ -0,0 +1,123 @@ +# File: 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) + +# Added PrintSupport and OpenGLWidgets which are often needed by QCustomPlot +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets SerialPort PrintSupport OpenGLWidgets) + +# --- QCustomPlot Integration --- +include(FetchContent) + +FetchContent_Declare( + QCustomPlot + URL https://www.qcustomplot.com/release/2.1.1/QCustomPlot.tar.gz + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) + +FetchContent_GetProperties(QCustomPlot) +if(NOT qcustomplot_POPULATED) + FetchContent_Populate(QCustomPlot) + # QCustomPlot source distribution doesn't have a CMakeLists.txt + add_library(QCustomPlot + ${qcustomplot_SOURCE_DIR}/qcustomplot.cpp + ${qcustomplot_SOURCE_DIR}/qcustomplot.h + ) + # Link required Qt modules to the library + target_link_libraries(QCustomPlot PUBLIC Qt6::Core Qt6::Gui Qt6::Widgets Qt6::PrintSupport) + target_include_directories(QCustomPlot PUBLIC ${qcustomplot_SOURCE_DIR}) +endif() + +# --- Icon Generation --- + +set(ICON_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/assets/icon_source.png") +set(ICON_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_icons.sh") +set(MACOS_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/app_icon.icns") +set(WINDOWS_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/app_icon.ico") + +# Find ImageMagick 'magick' executable +find_program(MAGICK_EXECUTABLE NAMES magick) +if(NOT MAGICK_EXECUTABLE) + message(WARNING "ImageMagick 'magick' not found. Icons will not be generated.") +endif() + +if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE) + execute_process(COMMAND chmod +x "${ICON_SCRIPT}") + + add_custom_command( + OUTPUT "${MACOS_ICON}" "${WINDOWS_ICON}" + COMMAND "${ICON_SCRIPT}" "${MAGICK_EXECUTABLE}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + DEPENDS "${ICON_SOURCE}" "${ICON_SCRIPT}" + COMMENT "Generating icons from source using ${MAGICK_EXECUTABLE}..." + VERBATIM + ) + + add_custom_target(GenerateIcons DEPENDS "${MACOS_ICON}" "${WINDOWS_ICON}") +endif() + +# --- Sources --- + +set(PROJECT_SOURCES + src/main.cpp + src/MainWindow.cpp + src/GraphWidget.cpp +) + +if(EXISTS "${ICON_SOURCE}") + list(APPEND PROJECT_SOURCES ${MACOS_ICON} ${WINDOWS_ICON}) +endif() + +set(PROJECT_HEADERS + src/MainWindow.h + src/GraphWidget.h +) + +if(ANDROID) + add_library(EISConfigurator SHARED ${PROJECT_SOURCES} ${PROJECT_HEADERS}) +else() + add_executable(EISConfigurator MANUAL_FINALIZATION ${PROJECT_SOURCES} ${PROJECT_HEADERS}) +endif() + +if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE) + add_dependencies(EISConfigurator GenerateIcons) +endif() + +target_link_libraries(EISConfigurator PRIVATE + Qt6::Core Qt6::Gui Qt6::Widgets Qt6::SerialPort + QCustomPlot +) + +if(ANDROID) + set_target_properties(EISConfigurator PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android) +endif() + +if(APPLE) + 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 + ) + if(EXISTS "${WINDOWS_ICON}") + set_target_properties(EISConfigurator PROPERTIES + WIN32_ICON "${WINDOWS_ICON}" + ) + endif() +endif() + +if(NOT ANDROID) + qt_finalize_executable(EISConfigurator) +endif() \ No newline at end of file diff --git a/host/Makefile b/host/Makefile new file mode 100644 index 0000000..3b69d08 --- /dev/null +++ b/host/Makefile @@ -0,0 +1,95 @@ +# 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 +# CRITICAL FIX: 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 \ No newline at end of file diff --git a/host/android/AndroidManifest.xml b/host/android/AndroidManifest.xml new file mode 100644 index 0000000..26199f8 --- /dev/null +++ b/host/android/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/host/ios/Info.plist b/host/ios/Info.plist new file mode 100644 index 0000000..644f189 --- /dev/null +++ b/host/ios/Info.plist @@ -0,0 +1,78 @@ + + + + + CFBundleDisplayName + Yr Crystals + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiresFullScreen + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + + NSMicrophoneUsageDescription + This app requires audio access to visualize music. + NSAppleMusicUsageDescription + This app requires access to your music library to play tracks. + NSPhotoLibraryUsageDescription + This app requires access to the photo library to load album art. + NSCameraUsageDescription + This app requires camera access for visualizer input. + + + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + + + + CFBundleDocumentTypes + + + CFBundleTypeName + Audio + LSHandlerRank + Alternate + LSItemContentTypes + + public.audio + public.mp3 + public.mpeg-4-audio + public.folder + public.directory + public.content + public.data + + + + + UIBackgroundModes + + audio + + + \ No newline at end of file diff --git a/host/scripts/generate_icons.sh b/host/scripts/generate_icons.sh new file mode 100755 index 0000000..a0b2f61 --- /dev/null +++ b/host/scripts/generate_icons.sh @@ -0,0 +1,135 @@ +# scripts/generate_icons.sh + +#!/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" +OUT_DIR="assets/icons" + +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 + +mkdir -p "$OUT_DIR" + +# macOS +ICONSET="$OUT_DIR/icon.iconset" +mkdir -p "$ICONSET" + +"$MAGICK_BIN" "$SOURCE" -scale 16x16 "$ICONSET/icon_16x16.png" +"$MAGICK_BIN" "$SOURCE" -scale 32x32 "$ICONSET/icon_16x16@2x.png" +"$MAGICK_BIN" "$SOURCE" -scale 32x32 "$ICONSET/icon_32x32.png" +"$MAGICK_BIN" "$SOURCE" -scale 64x64 "$ICONSET/icon_32x32@2x.png" +"$MAGICK_BIN" "$SOURCE" -scale 128x128 "$ICONSET/icon_128x128.png" +"$MAGICK_BIN" "$SOURCE" -scale 256x256 "$ICONSET/icon_128x128@2x.png" +"$MAGICK_BIN" "$SOURCE" -scale 256x256 "$ICONSET/icon_256x256.png" +"$MAGICK_BIN" "$SOURCE" -scale 512x512 "$ICONSET/icon_256x256@2x.png" +"$MAGICK_BIN" "$SOURCE" -scale 512x512 "$ICONSET/icon_512x512.png" +"$MAGICK_BIN" "$SOURCE" -scale 1024x1024 "$ICONSET/icon_512x512@2x.png" + +iconutil -c icns "$ICONSET" -o "$OUT_DIR/app_icon.icns" +rm -rf "$ICONSET" + +# Windows +"$MAGICK_BIN" "$SOURCE" \ + \( -clone 0 -scale 256x256 \) \ + \( -clone 0 -scale 128x128 \) \ + \( -clone 0 -scale 64x64 \) \ + \( -clone 0 -scale 48x48 \) \ + \( -clone 0 -scale 32x32 \) \ + \( -clone 0 -scale 16x16 \) \ + -delete 0 -alpha off -colors 256 "$OUT_DIR/app_icon.ico" + +# Android +ANDROID_DIR="$OUT_DIR/android/res" + +mkdir -p "$ANDROID_DIR/mipmap-mdpi" +"$MAGICK_BIN" "$SOURCE" -scale 48x48 "$ANDROID_DIR/mipmap-mdpi/ic_launcher.png" + +mkdir -p "$ANDROID_DIR/mipmap-hdpi" +"$MAGICK_BIN" "$SOURCE" -scale 72x72 "$ANDROID_DIR/mipmap-hdpi/ic_launcher.png" + +mkdir -p "$ANDROID_DIR/mipmap-xhdpi" +"$MAGICK_BIN" "$SOURCE" -scale 96x96 "$ANDROID_DIR/mipmap-xhdpi/ic_launcher.png" + +mkdir -p "$ANDROID_DIR/mipmap-xxhdpi" +"$MAGICK_BIN" "$SOURCE" -scale 144x144 "$ANDROID_DIR/mipmap-xxhdpi/ic_launcher.png" + +mkdir -p "$ANDROID_DIR/mipmap-xxxhdpi" +"$MAGICK_BIN" "$SOURCE" -scale 192x192 "$ANDROID_DIR/mipmap-xxxhdpi/ic_launcher.png" + +# iOS +XCASSETS_DIR="$OUT_DIR/ios/Assets.xcassets" +IOS_DIR="$XCASSETS_DIR/AppIcon.appiconset" +mkdir -p "$IOS_DIR" + +cat > "$XCASSETS_DIR/Contents.json" < "$IOS_DIR/Contents.json" < + +GraphWidget::GraphWidget(QWidget *parent) : QWidget(parent) +{ + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + + plot = new JKQTPlotter(this); + layout->addWidget(plot); + + // Initialize Graphs + graphY1 = new JKQTPXYLineGraph(plot); + graphY2 = new JKQTPXYLineGraph(plot); + + // Styling Y1 (e.g., Blue) + graphY1->setColor(QColor("blue")); + graphY1->setSymbolColor(QColor("blue")); + graphY1->setSymbol(JKQTPCircle); + graphY1->setSymbolSize(7); + graphY1->setLineWidth(2); + + // Styling Y2 (e.g., Red) + graphY2->setColor(QColor("red")); + graphY2->setSymbolColor(QColor("red")); + graphY2->setSymbol(JKQTPTriangle); + graphY2->setSymbolSize(7); + graphY2->setLineWidth(2); + + plot->addGraph(graphY1); + plot->addGraph(graphY2); +} + +void GraphWidget::setupPlot(QString title, QString xLabel, QString y1Label, QString y2Label) +{ + // Basic Settings + plot->getPlotter()->setPlotLabel(title); + plot->getXAxis()->setAxisLabel(xLabel); + + // Y-Axis setup + plot->getYAxis()->setAxisLabel(y1Label); // Left Axis + + // If we want a secondary axis for Y2, JKQTPlotter supports it, + // but for simplicity here we might plot on the same if scales are similar, + // or rely on user interpretation. + // For Bode (Ohm vs Degree), scales are vastly different. + // Ideally, Y2 should be on the right axis. + + // Assign Y1 to Left Axis + graphY1->setTitle(y1Label); + + // Assign Y2 to Right Axis (if supported by specific JKQT config, otherwise same axis) + // Assuming simple usage for now: + graphY2->setTitle(y2Label); + + // Enable Legend + plot->getPlotter()->setShowKey(true); +} + +void GraphWidget::setLogAxis(bool logX, bool logY) +{ + plot->getXAxis()->setLogAxis(logX); + plot->getYAxis()->setLogAxis(logY); +} + +void GraphWidget::addData(double x, double y1, double y2) +{ + xData.append(x); + y1Data.append(y1); + y2Data.append(y2); + + // Update graph data pointers + graphY1->setXColumn(xData); + graphY1->setYColumn(y1Data); + + graphY2->setXColumn(xData); + graphY2->setYColumn(y2Data); + + // Redraw + plot->redrawPlot(); +} + +void GraphWidget::clear() +{ + xData.clear(); + y1Data.clear(); + y2Data.clear(); + plot->redrawPlot(); +} \ No newline at end of file diff --git a/host/src/GraphWidget.h b/host/src/GraphWidget.h new file mode 100644 index 0000000..e5332bf --- /dev/null +++ b/host/src/GraphWidget.h @@ -0,0 +1,39 @@ +#ifndef GRAPHWIDGET_H +#define GRAPHWIDGET_H + +#include +#include +#include "jkqtplotter/jkqtplotter.h" +#include "jkqtplotter/graphs/jkqtpigraph.h" +#include "jkqtplotter/graphs/jkqtpimage.h" +#include "jkqtplotter/graphs/jkqtpscatter.h" +#include "jkqtplotter/graphs/jkqtplyines.h" + +class GraphWidget : public QWidget +{ + Q_OBJECT +public: + explicit GraphWidget(QWidget *parent = nullptr); + + // Configuration + void setupPlot(QString title, QString xLabel, QString y1Label, QString y2Label); + void setLogAxis(bool logX, bool logY); + + // Data Access + void addData(double x, double y1, double y2); + void clear(); + +private: + JKQTPlotter *plot; + + // Internal Data Storage + QVector xData; + QVector y1Data; + QVector y2Data; + + // Graph Objects + JKQTPXYLineGraph *graphY1; + JKQTPXYLineGraph *graphY2; +}; + +#endif // GRAPHWIDGET_H \ No newline at end of file diff --git a/host/src/MainWindow.cpp b/host/src/MainWindow.cpp new file mode 100644 index 0000000..1c22e65 --- /dev/null +++ b/host/src/MainWindow.cpp @@ -0,0 +1,228 @@ +#include "MainWindow.h" +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) +{ + serial = new QSerialPort(this); + connect(serial, &QSerialPort::readyRead, this, &MainWindow::onReadData); + + setupUi(); +} + +MainWindow::~MainWindow() +{ + if (serial->isOpen()) + serial->close(); +} + +void MainWindow::setupUi() +{ + centralWidget = new QWidget(this); + setCentralWidget(centralWidget); + QVBoxLayout *mainLayout = new QVBoxLayout(centralWidget); + + // --- Toolbar Area --- + QHBoxLayout *toolLayout = new QHBoxLayout(); + + portSelector = new QComboBox(); + const auto ports = QSerialPortInfo::availablePorts(); + for (const QSerialPortInfo &info : ports) { + portSelector->addItem(info.portName()); + } + + btnConnect = new QPushButton("Connect"); + connect(btnConnect, &QPushButton::clicked, [this]() { + onPortToggled(serial->isOpen()); + }); + + btnMeasure = new QPushButton("Measure"); + btnMeasure->setEnabled(false); + connect(btnMeasure, &QPushButton::clicked, this, &MainWindow::onMeasureClicked); + + btnCalibrate = new QPushButton("Calibrate"); + btnCalibrate->setEnabled(false); + connect(btnCalibrate, &QPushButton::clicked, this, &MainWindow::onCalibrateClicked); + + viewSelector = new QComboBox(); + viewSelector->addItem("Tabs (Swipe)"); + viewSelector->addItem("Split (Dual)"); + connect(viewSelector, QOverload::of(&QComboBox::currentIndexChanged), this, &MainWindow::onViewModeChanged); + + toolLayout->addWidget(portSelector); + toolLayout->addWidget(btnConnect); + toolLayout->addWidget(btnMeasure); + toolLayout->addWidget(btnCalibrate); + toolLayout->addWidget(viewSelector); // Add view switcher + + mainLayout->addLayout(toolLayout); + + // --- Graphs Initialization --- + finalGraph = new GraphWidget(); + finalGraph->setupPlot("Bode Plot", "Frequency (Hz)", "Impedance (Ohm)", "Phase (Rad)"); + finalGraph->setLogAxis(true, true); // Log-Log for Bode + + rawGraph = new GraphWidget(); + rawGraph->setupPlot("Raw Data (DFT)", "Frequency (Hz)", "Real", "Imaginary"); + rawGraph->setLogAxis(true, false); // Log X, Linear Y (Raw data can be neg) + + // --- Layout Containers --- + + // 1. Tab Widget (Mobile Style) + tabWidget = new QTabWidget(); + tabWidget->addTab(finalGraph, "Final Data"); + tabWidget->addTab(rawGraph, "Raw Data"); + + // 2. Splitter (Desktop Style) + splitter = new QSplitter(Qt::Horizontal); + // Note: We can't add the same widget to two parents. + // We will reparent them in onViewModeChanged. + + // Default to Tabs (Mobile friendly default) + mainLayout->addWidget(tabWidget); + + // Status Bar + statusLabel = new QLabel("Ready"); + statusBar()->addWidget(statusLabel); + + // Auto-detect Platform for default view + #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + viewSelector->setCurrentIndex(0); // Tabs + viewSelector->setVisible(false); // Hide selector on mobile, force tabs + #else + viewSelector->setCurrentIndex(1); // Default to Split on Desktop + #endif +} + +void MainWindow::onViewModeChanged(int index) +{ + // 0 = Tabs, 1 = Split + + // Remove graphs from their current parents + finalGraph->setParent(nullptr); + rawGraph->setParent(nullptr); + + QVBoxLayout *layout = qobject_cast(centralWidget->layout()); + + if (index == 0) { // Tabs + if (splitter->isVisible()) { + layout->removeWidget(splitter); + splitter->hide(); + } + + tabWidget->clear(); + tabWidget->addTab(finalGraph, "Final Data"); + tabWidget->addTab(rawGraph, "Raw Data"); + + if (!tabWidget->isVisible()) { + layout->addWidget(tabWidget); + tabWidget->show(); + } + } + else { // Split + if (tabWidget->isVisible()) { + layout->removeWidget(tabWidget); + tabWidget->hide(); + } + + splitter->addWidget(finalGraph); + splitter->addWidget(rawGraph); + + if (!splitter->isVisible()) { + layout->addWidget(splitter); + splitter->show(); + } + } +} + +void MainWindow::onPortToggled(bool connected) +{ + if (connected) { + serial->close(); + btnConnect->setText("Connect"); + btnMeasure->setEnabled(false); + btnCalibrate->setEnabled(false); + statusLabel->setText("Disconnected"); + } else { + serial->setPortName(portSelector->currentText()); + serial->setBaudRate(115200); // Or whatever your Pico uses, standard 115200 usually + + if (serial->open(QIODevice::ReadWrite)) { + btnConnect->setText("Disconnect"); + btnMeasure->setEnabled(true); + btnCalibrate->setEnabled(true); + statusLabel->setText("Connected"); + + // Clear old data on connect + finalGraph->clear(); + rawGraph->clear(); + } else { + QMessageBox::critical(this, "Error", "Could not open serial port."); + } + } +} + +void MainWindow::onMeasureClicked() +{ + if (serial->isOpen()) { + finalGraph->clear(); + rawGraph->clear(); + // Trigger a sweep or single measurement + // Example command logic + // For single: serial->write("MEAS 10000\n"); + // For sweep logic, the MCU might handle it or we loop here. + // Assuming user triggers 'MEAS 1000' style commands manually or logic exists. + // Let's just send a command if needed, or rely on user knowing what to do. + // For now, let's assume we want to measure 10kHz as a test + serial->write("MEAS 10000\n"); + } +} + +void MainWindow::onCalibrateClicked() +{ + if (serial->isOpen()) { + serial->write("CAL\n"); + } +} + +void MainWindow::onReadData() +{ + while (serial->canReadLine()) { + QByteArray line = serial->readLine().trimmed(); + processLine(line); + } +} + +void MainWindow::processLine(const QByteArray &line) +{ + if (line.startsWith("DATA")) { + // Format: DATA,freq,impedance,phase,raw_real,raw_imag + float freq, imp, phase; + int raw_real, raw_imag; + + // sscanf is dangerous with comma delimited strings if not careful, but works for fixed format + int count = sscanf(line.constData(), "DATA,%f,%f,%f,%d,%d", &freq, &imp, &phase, &raw_real, &raw_imag); + + if (count >= 3) { + // We have at least the final data + finalGraph->addData(freq, imp, phase); // Y1=Imp, Y2=Phase + } + + if (count >= 5) { + // We have raw data too + rawGraph->addData(freq, (double)raw_real, (double)raw_imag); + } + + statusLabel->setText(QString("Received: %1 Hz").arg(freq)); + } + else if (line.startsWith("LOG")) { + statusLabel->setText(line); + qDebug() << "Device Log:" << line; + } +} \ No newline at end of file diff --git a/host/src/MainWindow.h b/host/src/MainWindow.h new file mode 100644 index 0000000..ef0d0d9 --- /dev/null +++ b/host/src/MainWindow.h @@ -0,0 +1,53 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include "GraphWidget.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void onPortToggled(bool connected); + void onReadData(); + void onMeasureClicked(); + void onCalibrateClicked(); + void onViewModeChanged(int index); // New slot for desktop view switching + +private: + void setupUi(); + void processLine(const QByteArray &line); + + // Hardware + QSerialPort *serial; + + // UI Elements + QComboBox *portSelector; + QPushButton *btnConnect; + QPushButton *btnMeasure; + QPushButton *btnCalibrate; + QLabel *statusLabel; + + // Graphs + GraphWidget *finalGraph; // Bode Plot + GraphWidget *rawGraph; // Raw Real/Imag Plot + + // Layout Containers + QWidget *centralWidget; + QTabWidget *tabWidget; // For Mobile/Tab Mode + QSplitter *splitter; // For Desktop/Split Mode + QComboBox *viewSelector; // For Desktop to toggle modes +}; + +#endif // MAINWINDOW_H \ No newline at end of file diff --git a/host/src/main.cpp b/host/src/main.cpp new file mode 100644 index 0000000..9273b2c --- /dev/null +++ b/host/src/main.cpp @@ -0,0 +1,25 @@ +// File: aluf/src/main.cpp +#include +#include "MainWindow.h" + +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + QApplication::setApplicationName("EIS Configurator"); + QApplication::setApplicationVersion("1.0"); + + // Dark Theme + QPalette p = app.palette(); + p.setColor(QPalette::Window, QColor(30, 30, 30)); + p.setColor(QPalette::WindowText, Qt::white); + p.setColor(QPalette::Base, QColor(15, 15, 15)); + p.setColor(QPalette::AlternateBase, QColor(45, 45, 45)); + p.setColor(QPalette::Text, Qt::white); + p.setColor(QPalette::Button, QColor(45, 45, 45)); + p.setColor(QPalette::ButtonText, Qt::white); + app.setPalette(p); + + MainWindow w; + w.show(); + + return app.exec(); +} \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..dfaa850 --- /dev/null +++ b/main.c @@ -0,0 +1,474 @@ +// File: EIS/main.c +#include +#include +#include +#include +#include "pico/stdlib.h" +#include "hardware/spi.h" +#include "hardware/gpio.h" +#include "ad5940.h" + +// -------------------------------------------------------------------------- +// Pin Definitions +// -------------------------------------------------------------------------- +#define PIN_MISO 0 +#define PIN_CS 1 +#define PIN_SCK 2 +#define PIN_MOSI 3 +#define PIN_RESET 9 +#define PIN_AD_INTERRUPT 29 + +#define SPI_PORT spi0 +#define SPI_BAUDRATE_HIGH 500000 + +// -------------------------------------------------------------------------- +// Switch Matrix Bit Definitions +// -------------------------------------------------------------------------- +#ifndef SWT_SE0 +#define SWT_SE0 (1 << 6) // T7 +#endif +#ifndef SWT_T9 +#define SWT_T9 (1 << 8) // T9 +#endif +#ifndef SWT_TR1 +#define SWT_TR1 (1 << 11) // TR1 +#endif + +// DSWFULLCON +#ifndef SWD_AIN2 +#define SWD_AIN2 (1 << 2) // D3 +#endif +#ifndef SWD_CE0 +#define SWD_CE0 (1 << 4) // D5 +#endif + +// TSWFULLCON +#ifndef SWT_AIN3 +#define SWT_AIN3 (1 << 3) // T4 +#endif + +// -------------------------------------------------------------------------- +// AD5940 Library Interface +// -------------------------------------------------------------------------- + +void AD5940_CsClr(void) { + gpio_put(PIN_CS, 0); + sleep_us(1); +} + +void AD5940_CsSet(void) { + sleep_us(1); + gpio_put(PIN_CS, 1); + sleep_us(1); +} + +void AD5940_RstClr(void) { gpio_put(PIN_RESET, 0); } +void AD5940_RstSet(void) { gpio_put(PIN_RESET, 1); } +void AD5940_Delay10us(uint32_t time) { sleep_us(time * 10); } + +uint32_t AD5940_GetMCUIntFlag(void) { + uint32_t flag = AD5940_ReadReg(REG_INTC_INTCFLAG0); + return (flag != 0); +} + +void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuffer, unsigned long length) { + if (pRecvBuffer == NULL) { + if (pSendBuffer != NULL) { + spi_write_blocking(SPI_PORT, pSendBuffer, length); + } + } else { + if (pSendBuffer == NULL) { + uint8_t dummy = 0x00; + for (unsigned long i = 0; i < length; i++) { + spi_write_read_blocking(SPI_PORT, &dummy, &pRecvBuffer[i], 1); + } + } else { + spi_write_read_blocking(SPI_PORT, pSendBuffer, pRecvBuffer, length); + } + } +} + +// -------------------------------------------------------------------------- +// Helpers +// -------------------------------------------------------------------------- + +void AD5940_PulseReset(void) { + AD5940_RstClr(); + sleep_ms(10); + AD5940_RstSet(); + sleep_ms(50); // Increased wait for boot stability +} + +// Helper to map Register Macro to Actual Sample Count for calculations +uint32_t GetDftSamples(uint32_t dft_num_macro) { + if (dft_num_macro == DFTNUM_256) return 256; + if (dft_num_macro == DFTNUM_512) return 512; + if (dft_num_macro == DFTNUM_1024) return 1024; + if (dft_num_macro == DFTNUM_2048) return 2048; + if (dft_num_macro == DFTNUM_4096) return 4096; + if (dft_num_macro == DFTNUM_8192) return 8192; + if (dft_num_macro == DFTNUM_16384) return 16384; + return 2048; // Safe default +} + +// Structure to hold calculated configuration +typedef struct { + uint32_t DftNum; + uint32_t DftSrc; + uint32_t Sinc3Osr; + uint32_t Sinc2Osr; + float SampleRate; // Estimated +} FilterConfig_Type; + +// Calculates optimal DFT and Filter settings based on frequency +// Optimized for High Speed Loop (HFOSC 16MHz) only +void AD5940_GetFilterCfg(float freq, FilterConfig_Type *pCfg) { + // Strategy: + // For > 50Hz, use SINC3 (Fast, ~200kHz) + // For <= 50Hz, use SINC2 path (Slower, ~9kHz) to capture full waves + + if (freq >= 50.0f) { + pCfg->DftSrc = DFTSRC_SINC3; + pCfg->Sinc3Osr = ADCSINC3OSR_4; + pCfg->Sinc2Osr = ADCSINC2OSR_22; // Don't care for SINC3 src + pCfg->SampleRate = 200000.0f; // 800k / 4 + + // Adjust DFT size for speed vs accuracy at high freq + if (freq > 40000.0f) pCfg->DftNum = DFTNUM_2048; + else if (freq > 1000.0f) pCfg->DftNum = DFTNUM_4096; + else pCfg->DftNum = DFTNUM_8192; + } + else { + // Low Frequency High Speed (e.g. 10Hz) + // Use SINC2 filter to slow down data rate + // Rate = 800k / (4 * 22) = 9090 Hz + pCfg->DftSrc = DFTSRC_SINC2NOTCH; + pCfg->Sinc3Osr = ADCSINC3OSR_4; + pCfg->Sinc2Osr = ADCSINC2OSR_22; + pCfg->SampleRate = 9090.0f; + + // 8192 samples @ 9kHz = ~0.9 seconds + // Covers 9 cycles of 10Hz. Perfect. + pCfg->DftNum = DFTNUM_8192; + } +} + +// -------------------------------------------------------------------------- +// Measurement Functions +// -------------------------------------------------------------------------- + +static uint32_t AppBuff[512]; + +// Internal Helper for Calibration +static AD5940Err RunCalibration(void) +{ + ADCPGACal_Type pga_cal; + + pga_cal.AdcClkFreq = 16000000.0; + pga_cal.SysClkFreq = 16000000.0; + pga_cal.ADCPga = ADCPGA_1; + pga_cal.ADCSinc2Osr = ADCSINC2OSR_1333; + pga_cal.ADCSinc3Osr = ADCSINC3OSR_4; + pga_cal.TimeOut10us = 100000; + pga_cal.VRef1p82 = 1.82; + pga_cal.VRef1p11 = 1.11; + pga_cal.PGACalType = PGACALTYPE_OFFSETGAIN; + + return AD5940_ADCPGACal(&pga_cal); +} + +// Command Handler for "CAL" +static void AppADCPgaCal(void) +{ + printf("LOG,Preparing for Calibration...\n"); + // Ensure clean state before CAL + AD5940_PulseReset(); + AD5940_Initialize(); + AD5940_WriteReg(REG_INTC_INTCSEL0, 0xFFFFFFFF); + AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF); + + AD5940Err err = RunCalibration(); + if(err != AD5940ERR_OK) printf("LOG,ADC Cal Failed: %d\n", err); + else printf("LOG,ADC Cal Passed.\n"); + + // Cleanup: Turn off everything after CAL to leave clean slate + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); +} + +// High Speed Loop (HSTIA + HSDAC) for ALL Frequencies +void MeasureHighSpeed(float freq) +{ + // 1. HARD RESET & Re-Initialize + AD5940_PulseReset(); + AD5940_Initialize(); + AD5940_WriteReg(REG_INTC_INTCSEL0, 0xFFFFFFFF); + AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF); + + // 2. Calibrate + // Note: Calibration routines are invasive. They might leave the AFE in weird states. + AD5940Err calErr = RunCalibration(); + if(calErr != AD5940ERR_OK) { + printf("LOG,Auto-Calibration Failed: %d\n", calErr); + } + + // CRITICAL: Shut down EVERYTHING after calibration. + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); + + // CRITICAL: Disable Sequencer + AD5940_SEQCtrlS(bFALSE); + + // 3. Clock Configuration + AD5940_WriteReg(REG_ALLON_OSCKEY, 0xCB14); + AD5940_WriteReg(REG_ALLON_OSCCON, 0x0003); // Enable HFOSC + sleep_ms(2); + + AD5940_WriteReg(REG_AFE_LPMODEKEY, 0xC59D6); + AD5940_WriteReg(REG_AFE_LPMODECLKSEL, 0); // Select HFOSC + + spi_set_baudrate(SPI_PORT, SPI_BAUDRATE_HIGH); + + // 4. Switch Matrix Cleanup + AD5940_WriteReg(REG_AFE_LPTIASW0, 0x0000); + AD5940_WriteReg(REG_AFE_LPDACSW0, 0x0000); + + // 5. Configure Loop + HSLoopCfg_Type hs_loop; + hs_loop.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2; + hs_loop.HsDacCfg.HsDacGain = HSDACGAIN_1; + hs_loop.HsDacCfg.HsDacUpdateRate = 7; + + if (freq > 1000.0f) { + hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_1K; + } else { + hs_loop.HsTiaCfg.HstiaRtiaSel = HSTIARTIA_5K; + } + + hs_loop.HsTiaCfg.HstiaBias = HSTIABIAS_1P1; + hs_loop.HsTiaCfg.HstiaCtia = 16; + hs_loop.HsTiaCfg.DiodeClose = bFALSE; + + AD5940_HSLoopCfgS(&hs_loop); + + // 6. Switch Matrix (S+/S-) + AD5940_WriteReg(REG_AFE_SWCON, BITM_AFE_SWCON_SWSOURCESEL); + AD5940_WriteReg(REG_AFE_DSWFULLCON, SWD_CE0); + AD5940_WriteReg(REG_AFE_PSWFULLCON, 0x0000); + AD5940_WriteReg(REG_AFE_NSWFULLCON, 0x0000); + AD5940_WriteReg(REG_AFE_TSWFULLCON, SWT_SE0 | SWT_T9); + + // 7. Get Dynamic Configuration + FilterConfig_Type cfg; + AD5940_GetFilterCfg(freq, &cfg); + + // 8. ADC Configuration + ADCBaseCfg_Type adc_cfg; + memset(&adc_cfg, 0, sizeof(adc_cfg)); + adc_cfg.ADCMuxP = ADCMUXP_HSTIA_P; + adc_cfg.ADCMuxN = ADCMUXN_HSTIA_N; + adc_cfg.ADCPga = ADCPGA_1; + AD5940_ADCBaseCfgS(&adc_cfg); + + // Initialize and Enable Clocks + ADCFilterCfg_Type filter_cfg; + memset(&filter_cfg, 0, sizeof(filter_cfg)); + + filter_cfg.ADCSinc3Osr = cfg.Sinc3Osr; + filter_cfg.ADCSinc2Osr = cfg.Sinc2Osr; + filter_cfg.ADCAvgNum = ADCAVGNUM_16; + filter_cfg.ADCRate = ADCRATE_800KHZ; + filter_cfg.BpNotch = bTRUE; + filter_cfg.BpSinc3 = bFALSE; + filter_cfg.Sinc2NotchEnable = bTRUE; + + // Explicitly Enable Clocks + filter_cfg.DFTClkEnable = bTRUE; + filter_cfg.WGClkEnable = bTRUE; + filter_cfg.Sinc3ClkEnable = bTRUE; + filter_cfg.Sinc2NotchClkEnable = bTRUE; + + AD5940_ADCFilterCfgS(&filter_cfg); + + // Configure ADC Buffers + AD5940_WriteReg(REG_AFE_ADCBUFCON, 0x005F3D0F); + + // 9. Waveform + AD5940_WriteReg(REG_AFE_WGFCW, AD5940_WGFreqWordCal(freq, 16000000.0)); + AD5940_WriteReg(REG_AFE_WGAMPLITUDE, 2047); + AD5940_WriteReg(REG_AFE_WGOFFSET, 0); + AD5940_WriteReg(REG_AFE_WGPHASE, 0); + AD5940_WriteReg(REG_AFE_WGCON, WGTYPE_SIN); + + // 10. DFT + DFTCfg_Type dft_cfg; + dft_cfg.DftNum = cfg.DftNum; + dft_cfg.DftSrc = cfg.DftSrc; + dft_cfg.HanWinEn = bTRUE; + AD5940_DFTCfgS(&dft_cfg); + + // 11. Configure FIFO for DFT + // Ensure we capture DFT results in FIFO + FIFOCfg_Type fifo_cfg; + fifo_cfg.FIFOEn = bTRUE; + fifo_cfg.FIFOMode = FIFOMODE_FIFO; + fifo_cfg.FIFOSize = FIFOSIZE_4KB; + fifo_cfg.FIFOSrc = FIFOSRC_DFT; + fifo_cfg.FIFOThresh = 4; + AD5940_FIFOCfg(&fifo_cfg); + + // 12. Run + + // Power Up Analog Blocks + AD5940_AFECtrlS(AFECTRL_HSDACPWR|AFECTRL_HSTIAPWR|AFECTRL_ADCPWR|AFECTRL_DACREFPWR|AFECTRL_EXTBUFPWR|AFECTRL_INAMPPWR, bTRUE); + + sleep_ms(20); + + // CRITICAL FIX: Force AFECON Clean + uint32_t current_afecon = AD5940_ReadReg(REG_AFE_AFECON); + current_afecon &= ~(0x00003000); // Clear Temp bits + AD5940_WriteReg(REG_AFE_AFECON, current_afecon); + + // Enable ADC Repeat Mode + AD5940_ADCRepeatCfgS(0xffffffff); + + // Clear and Enable Interrupts + AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF); + AD5940_WriteReg(REG_INTC_INTCSEL0, 0xFFFFFFFF); + + // Start Conversion + AD5940_AFECtrlS(AFECTRL_ADCCNV|AFECTRL_DFT|AFECTRL_WG, bTRUE); + + // Calculate Timeout using Helper + uint32_t sample_count = GetDftSamples(cfg.DftNum); + float duration = (float)sample_count / cfg.SampleRate; + uint32_t timeout_us = (uint32_t)(duration * 1000000.0f) + 2000000; + + // Polling Loop with Exit Condition + uint32_t start_time = to_us_since_boot(get_absolute_time()); + bool success = false; + + // Results + int32_t real = 0; + int32_t image = 0; + + while((to_us_since_boot(get_absolute_time()) - start_time) < timeout_us) { + + // Polling Strategy: Check FIFO Count instead of Interrupt Flags + // One DFT result = 2 words (Real + Image). We wait for >= 2 words. + uint32_t fifo_cnt = (AD5940_ReadReg(REG_AFE_FIFOCNTSTA) & 0x07FF0000) >> 16; + + if(fifo_cnt >= 2) { + // Read result from FIFO + uint32_t buffer[2]; + AD5940_FIFORd(buffer, 2); + real = (int32_t)buffer[0]; + image = (int32_t)buffer[1]; + + AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF); + success = true; + break; + } + sleep_us(100); + } + + if (!success) { + uint32_t flags = AD5940_ReadReg(REG_INTC_INTCFLAG0); + uint32_t afecon_read = AD5940_ReadReg(REG_AFE_AFECON); + uint32_t fifo_cnt = (AD5940_ReadReg(REG_AFE_FIFOCNTSTA) & 0x07FF0000) >> 16; + printf("LOG,Error: Measurement Timeout (HS). Exp Duration: %.2fs. Flags: 0x%08X, AFECON: 0x%08X, FIFO: %d\n", duration, flags, afecon_read, fifo_cnt); + } + + // 13. Process Result + // Sign Extend 18-bit to 32-bit + // Store original raw values before processing + int32_t raw_real = real; + int32_t raw_image = image; + + if(real & (1<<17)) real |= 0xFFFC0000; + if(image & (1<<17)) image |= 0xFFFC0000; + + float mag = sqrt((float)real*real + (float)image*image); + float rtia_val = (freq > 1000.0f) ? 1000.0f : 5000.0f; + float impedance = (mag > 0) ? (2047.0f / mag) * rtia_val : 0; + float phase = atan2((float)image, (float)real); + + // Updated Output Format: Freq, Impedance, Phase, RawReal, RawImag + printf("DATA,%.2f,%.4f,%.4f,%d,%d\n", freq, impedance, phase, raw_real, raw_image); + + AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); +} + +// -------------------------------------------------------------------------- +// Main +// -------------------------------------------------------------------------- + +void process_command(char* cmd) { + char* token = strtok(cmd, " "); + if (!token) return; + + if (strcmp(token, "CAL") == 0) { + AppADCPgaCal(); + } + else if (strcmp(token, "MEAS") == 0) { + char* arg1 = strtok(NULL, " "); + if (arg1) { + float freq = strtof(arg1, NULL); + if (freq < 10.0f) { + freq = 10.0f; + } + MeasureHighSpeed(freq); + } + } + else if (strcmp(token, "ID") == 0) { + uint32_t chip_id = AD5940_ReadReg(REG_AFECON_CHIPID); + printf("LOG,Chip ID: 0x%04X\n", chip_id); + } +} + +int main() { + stdio_init_all(); + sleep_ms(1000); + + spi_init(SPI_PORT, SPI_BAUDRATE_HIGH); + spi_set_format(SPI_PORT, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + + gpio_set_function(PIN_MISO, GPIO_FUNC_SPI); + gpio_set_function(PIN_SCK, GPIO_FUNC_SPI); + gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI); + + gpio_init(PIN_CS); + gpio_set_dir(PIN_CS, GPIO_OUT); + gpio_put(PIN_CS, 1); + + gpio_init(PIN_RESET); + gpio_set_dir(PIN_RESET, GPIO_OUT); + gpio_put(PIN_RESET, 1); + + gpio_init(PIN_AD_INTERRUPT); + gpio_set_dir(PIN_AD_INTERRUPT, GPIO_IN); + gpio_pull_up(PIN_AD_INTERRUPT); + + // Initial Start + AD5940_PulseReset(); + AD5940_Initialize(); + AD5940_WriteReg(REG_INTC_INTCSEL0, 0xFFFFFFFF); + AD5940_WriteReg(REG_INTC_INTCCLR, 0xFFFFFFFF); + AD5940_SEQGenInit(AppBuff, 512); + + char buffer[64]; + int pos = 0; + + while (true) { + int c = getchar_timeout_us(1000); + if (c != PICO_ERROR_TIMEOUT) { + if (c == '\n' || c == '\r') { + if (pos > 0) { + buffer[pos] = 0; + process_command(buffer); + pos = 0; + } + } else if (pos < 63) { + buffer[pos++] = (char)c; + } + } + } +} \ No newline at end of file