#include "echem.h" #include "ad5940.h" #include "protocol.h" #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" /* LP RTIA register mapping */ const uint32_t lp_rtia_map[] = { [LP_RTIA_200] = LPTIARTIA_200R, [LP_RTIA_1K] = LPTIARTIA_1K, [LP_RTIA_2K] = LPTIARTIA_2K, [LP_RTIA_3K] = LPTIARTIA_3K, [LP_RTIA_4K] = LPTIARTIA_4K, [LP_RTIA_6K] = LPTIARTIA_6K, [LP_RTIA_8K] = LPTIARTIA_8K, [LP_RTIA_10K] = LPTIARTIA_10K, [LP_RTIA_12K] = LPTIARTIA_12K, [LP_RTIA_16K] = LPTIARTIA_16K, [LP_RTIA_20K] = LPTIARTIA_20K, [LP_RTIA_24K] = LPTIARTIA_24K, [LP_RTIA_30K] = LPTIARTIA_30K, [LP_RTIA_32K] = LPTIARTIA_32K, [LP_RTIA_40K] = LPTIARTIA_40K, [LP_RTIA_48K] = LPTIARTIA_48K, [LP_RTIA_64K] = LPTIARTIA_64K, [LP_RTIA_85K] = LPTIARTIA_85K, [LP_RTIA_96K] = LPTIARTIA_96K, [LP_RTIA_100K] = LPTIARTIA_100K, [LP_RTIA_120K] = LPTIARTIA_120K, [LP_RTIA_128K] = LPTIARTIA_128K, [LP_RTIA_160K] = LPTIARTIA_160K, [LP_RTIA_196K] = LPTIARTIA_196K, [LP_RTIA_256K] = LPTIARTIA_256K, [LP_RTIA_512K] = LPTIARTIA_512K, }; /* LP RTIA ohms for current conversion */ const float lp_rtia_ohms[] = { [LP_RTIA_200] = 200.0f, [LP_RTIA_1K] = 1000.0f, [LP_RTIA_2K] = 2000.0f, [LP_RTIA_3K] = 3000.0f, [LP_RTIA_4K] = 4000.0f, [LP_RTIA_6K] = 6000.0f, [LP_RTIA_8K] = 8000.0f, [LP_RTIA_10K] = 10000.0f, [LP_RTIA_12K] = 12000.0f, [LP_RTIA_16K] = 16000.0f, [LP_RTIA_20K] = 20000.0f, [LP_RTIA_24K] = 24000.0f, [LP_RTIA_30K] = 30000.0f, [LP_RTIA_32K] = 32000.0f, [LP_RTIA_40K] = 40000.0f, [LP_RTIA_48K] = 48000.0f, [LP_RTIA_64K] = 64000.0f, [LP_RTIA_85K] = 85000.0f, [LP_RTIA_96K] = 96000.0f, [LP_RTIA_100K] = 100000.0f, [LP_RTIA_120K] = 120000.0f, [LP_RTIA_128K] = 128000.0f, [LP_RTIA_160K] = 160000.0f, [LP_RTIA_196K] = 196000.0f, [LP_RTIA_256K] = 256000.0f, [LP_RTIA_512K] = 512000.0f, }; /* * LPDAC math (2.5V reference): * 6-bit DAC → VZERO: 200mV + code * 34.375mV (code 0-63) * 12-bit DAC → VBIAS: 200mV + code * 0.537mV (code 0-4095) * Cell potential: V_cell = VZERO - VBIAS * * VZERO fixed at ~1100mV (code 26). * VBIAS swept to set cell potential. */ #define VZERO_CODE 26 #define VZERO_MV (200.0f + VZERO_CODE * 34.375f) /* ~1093.75 mV */ #define VBIAS_OFFSET 200.0f #define VBIAS_LSB 0.537f static uint16_t mv_to_vbias_code(float v_cell_mv) { /* V_cell = VZERO - VBIAS → VBIAS = VZERO - V_cell */ float vbias_mv = VZERO_MV - v_cell_mv; float code = (vbias_mv - VBIAS_OFFSET) / VBIAS_LSB; if (code < 0) code = 0; if (code > 4095) code = 4095; return (uint16_t)(code + 0.5f); } static void echem_init_lp(uint32_t rtia_reg) { AD5940_SoftRst(); AD5940_Initialize(); CLKCfg_Type clk; memset(&clk, 0, sizeof(clk)); clk.HFOSCEn = bTRUE; clk.HfOSC32MHzMode = bFALSE; clk.SysClkSrc = SYSCLKSRC_HFOSC; clk.ADCCLkSrc = ADCCLKSRC_HFOSC; clk.SysClkDiv = SYSCLKDIV_1; clk.ADCClkDiv = ADCCLKDIV_1; clk.LFOSCEn = bTRUE; clk.HFXTALEn = bFALSE; AD5940_CLKCfg(&clk); AFERefCfg_Type ref; AD5940_StructInit(&ref, sizeof(ref)); ref.HpBandgapEn = bTRUE; ref.Hp1V1BuffEn = bTRUE; ref.Hp1V8BuffEn = bTRUE; ref.LpBandgapEn = bTRUE; ref.LpRefBufEn = bTRUE; ref.LpRefBoostEn = bTRUE; AD5940_REFCfgS(&ref); AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ); LPLoopCfg_Type lp; AD5940_StructInit(&lp, sizeof(lp)); lp.LpDacCfg.LpdacSel = LPDAC0; lp.LpDacCfg.LpDacSrc = LPDACSRC_MMR; lp.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT; lp.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; lp.LpDacCfg.LpDacSW = LPDACSW_VZERO2LPTIA | LPDACSW_VBIAS2LPPA; lp.LpDacCfg.LpDacRef = LPDACREF_2P5; lp.LpDacCfg.DataRst = bFALSE; lp.LpDacCfg.PowerEn = bTRUE; lp.LpDacCfg.DacData6Bit = VZERO_CODE; lp.LpDacCfg.DacData12Bit = mv_to_vbias_code(0); lp.LpAmpCfg.LpAmpSel = LPAMP0; lp.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_BOOST3; lp.LpAmpCfg.LpPaPwrEn = bTRUE; lp.LpAmpCfg.LpTiaPwrEn = bTRUE; lp.LpAmpCfg.LpTiaRf = LPTIARF_SHORT; lp.LpAmpCfg.LpTiaRload = LPTIARLOAD_SHORT; lp.LpAmpCfg.LpTiaRtia = rtia_reg; /* CE0=drive, RE0=ref, SE0=working: SW2,4,5,7,12 */ lp.LpAmpCfg.LpTiaSW = LPTIASW(2) | LPTIASW(4) | LPTIASW(5) | LPTIASW(7) | LPTIASW(12); AD5940_LPLoopCfgS(&lp); /* ADC: SINC2+Notch on LPTIA output */ ADCBaseCfg_Type adc; adc.ADCMuxP = ADCMUXP_LPTIA0_P; adc.ADCMuxN = ADCMUXN_LPTIA0_N; adc.ADCPga = ADCPGA_1P5; AD5940_ADCBaseCfgS(&adc); ADCFilterCfg_Type filt; AD5940_StructInit(&filt, sizeof(filt)); filt.ADCSinc3Osr = ADCSINC3OSR_4; filt.ADCSinc2Osr = ADCSINC2OSR_667; filt.ADCAvgNum = ADCAVGNUM_16; filt.ADCRate = ADCRATE_800KHZ; filt.BpNotch = bFALSE; filt.BpSinc3 = bFALSE; filt.Sinc2NotchEnable = bTRUE; filt.Sinc3ClkEnable = bTRUE; filt.Sinc2NotchClkEnable = bTRUE; filt.DFTClkEnable = bFALSE; filt.WGClkEnable = bFALSE; AD5940_ADCFilterCfgS(&filt); AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_SINC2RDY, bTRUE); AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bTRUE); AD5940_INTCClrFlag(AFEINTSRC_ALLINT); AGPIOCfg_Type gpio; AD5940_StructInit(&gpio, sizeof(gpio)); gpio.FuncSet = GP0_INT; gpio.OutputEnSet = AGPIO_Pin0; AD5940_AGPIOCfg(&gpio); AD5940_WriteReg(REG_AFE_FIFOCON, 0); } static float read_current_ua(float rtia_ohms) { AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY); AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE); AD5940_Delay10us(25); AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE); AD5940_ClrMCUIntFlag(); while (!AD5940_GetMCUIntFlag()) vTaskDelay(1); AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_ADCPWR, bFALSE); AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY); uint32_t raw = AD5940_ReadAfeResult(AFERESULT_SINC2); int32_t code = (raw & (1UL << 15)) ? (int32_t)(raw | 0xFFFF0000UL) : (int32_t)raw; /* clamp near ADC saturation to prevent sign-flip wrap artifact */ if (code > 32700) code = 32700; if (code < -32700) code = -32700; /* I = V_tia / RTIA, V_tia = code * Vref / (PGA * 32768) */ float v_tia = (float)code * 1.82f / (1.5f * 32768.0f); float i_a = v_tia / rtia_ohms; return i_a * 1e6f; /* convert to uA */ } static void echem_init_adc(void) { AD5940_SoftRst(); AD5940_Initialize(); CLKCfg_Type clk; memset(&clk, 0, sizeof(clk)); clk.HFOSCEn = bTRUE; clk.HfOSC32MHzMode = bFALSE; clk.SysClkSrc = SYSCLKSRC_HFOSC; clk.ADCCLkSrc = ADCCLKSRC_HFOSC; clk.SysClkDiv = SYSCLKDIV_1; clk.ADCClkDiv = ADCCLKDIV_1; clk.LFOSCEn = bTRUE; clk.HFXTALEn = bFALSE; AD5940_CLKCfg(&clk); AFERefCfg_Type ref; AD5940_StructInit(&ref, sizeof(ref)); ref.HpBandgapEn = bTRUE; ref.Hp1V1BuffEn = bTRUE; ref.Hp1V8BuffEn = bTRUE; ref.LpBandgapEn = bTRUE; ref.LpRefBufEn = bTRUE; ref.LpRefBoostEn = bTRUE; AD5940_REFCfgS(&ref); AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ); ADCFilterCfg_Type filt; AD5940_StructInit(&filt, sizeof(filt)); filt.ADCSinc3Osr = ADCSINC3OSR_4; filt.ADCSinc2Osr = ADCSINC2OSR_667; filt.ADCAvgNum = ADCAVGNUM_16; filt.ADCRate = ADCRATE_800KHZ; filt.BpNotch = bFALSE; filt.BpSinc3 = bFALSE; filt.Sinc2NotchEnable = bTRUE; filt.Sinc3ClkEnable = bTRUE; filt.Sinc2NotchClkEnable = bTRUE; filt.DFTClkEnable = bFALSE; filt.WGClkEnable = bFALSE; AD5940_ADCFilterCfgS(&filt); AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_SINC2RDY, bTRUE); AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_SINC2RDY, bTRUE); AD5940_INTCClrFlag(AFEINTSRC_ALLINT); AGPIOCfg_Type gpio; AD5940_StructInit(&gpio, sizeof(gpio)); gpio.FuncSet = GP0_INT; gpio.OutputEnSet = AGPIO_Pin0; AD5940_AGPIOCfg(&gpio); AD5940_WriteReg(REG_AFE_FIFOCON, 0); } static float read_voltage_mv(uint32_t muxp) { AD5940_ADCMuxCfgS(muxp, ADCMUXN_VSET1P1); AD5940_Delay10us(50); AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY); AD5940_AFECtrlS(AFECTRL_ADCPWR, bTRUE); AD5940_Delay10us(25); AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE); AD5940_ClrMCUIntFlag(); while (!AD5940_GetMCUIntFlag()) vTaskDelay(1); AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_ADCPWR, bFALSE); AD5940_INTCClrFlag(AFEINTSRC_SINC2RDY); uint32_t raw = AD5940_ReadAfeResult(AFERESULT_SINC2); int32_t code = (raw & (1UL << 15)) ? (int32_t)(raw | 0xFFFF0000UL) : (int32_t)raw; /* V_diff = code * Vref / (PGA * 32768), PGA=1.5, Vref=1.82V */ return (float)code * 1820.0f / (1.5f * 32768.0f); } static void echem_shutdown_lp(void) { LPLoopCfg_Type lp; AD5940_StructInit(&lp, sizeof(lp)); lp.LpDacCfg.LpdacSel = LPDAC0; lp.LpDacCfg.LpDacSrc = LPDACSRC_MMR; lp.LpDacCfg.LpDacVzeroMux = LPDACVZERO_6BIT; lp.LpDacCfg.LpDacVbiasMux = LPDACVBIAS_12BIT; lp.LpDacCfg.LpDacSW = 0; lp.LpDacCfg.LpDacRef = LPDACREF_2P5; lp.LpDacCfg.PowerEn = bFALSE; lp.LpDacCfg.DataRst = bFALSE; lp.LpDacCfg.DacData6Bit = 0; lp.LpDacCfg.DacData12Bit = 0; lp.LpAmpCfg.LpAmpSel = LPAMP0; lp.LpAmpCfg.LpAmpPwrMod = LPAMPPWR_NORM; lp.LpAmpCfg.LpPaPwrEn = bFALSE; lp.LpAmpCfg.LpTiaPwrEn = bFALSE; lp.LpAmpCfg.LpTiaRf = LPTIARF_OPEN; lp.LpAmpCfg.LpTiaRload = LPTIARLOAD_SHORT; lp.LpAmpCfg.LpTiaRtia = LPTIARTIA_OPEN; lp.LpAmpCfg.LpTiaSW = 0; AD5940_LPLoopCfgS(&lp); SWMatrixCfg_Type sw = { SWD_OPEN, SWP_OPEN, SWN_OPEN, SWT_OPEN }; AD5940_SWMatrixCfgS(&sw); } /* ---- public ---- */ int echem_clean(float v_mv, float duration_s) { echem_init_lp(LPTIARTIA_200R); uint16_t code = mv_to_vbias_code(v_mv); AD5940_LPDAC0WriteS(code, VZERO_CODE); printf("Clean: %.0f mV for %.0f s\n", v_mv, duration_s); vTaskDelay(pdMS_TO_TICKS((uint32_t)(duration_s * 1000.0f))); echem_shutdown_lp(); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); printf("Clean: done\n"); return 0; } void echem_default_lsv(LSVConfig *cfg) { memset(cfg, 0, sizeof(*cfg)); cfg->v_start = 0.0f; cfg->v_stop = 500.0f; cfg->scan_rate = 50.0f; cfg->lp_rtia = LP_RTIA_10K; } void echem_default_amp(AmpConfig *cfg) { memset(cfg, 0, sizeof(*cfg)); cfg->v_hold = 200.0f; cfg->interval_ms = 100.0f; cfg->duration_s = 60.0f; cfg->lp_rtia = LP_RTIA_10K; } static void lsv_calc_step(const LSVConfig *cfg, uint32_t max_points, uint32_t *n_out, float *step_out) { float v_range = cfg->v_stop - cfg->v_start; uint32_t n_lsb = (uint32_t)(fabsf(v_range / VBIAS_LSB) + 0.5f); uint32_t n_steps = n_lsb; uint32_t step_mult = 1; if (n_steps > max_points) { step_mult = (n_lsb + max_points - 1) / max_points; n_steps = (n_lsb + step_mult - 1) / step_mult; } if (n_steps < 2) n_steps = 2; *n_out = n_steps; *step_out = (v_range > 0) ? VBIAS_LSB * step_mult : -VBIAS_LSB * step_mult; } uint32_t echem_lsv_calc_steps(const LSVConfig *cfg, uint32_t max_points) { float v_range = cfg->v_stop - cfg->v_start; if (fabsf(v_range) < 0.001f) return 0; uint32_t n; float step; lsv_calc_step(cfg, max_points, &n, &step); return n; } int echem_lsv(const LSVConfig *cfg, LSVPoint *out, uint32_t max_points, lsv_point_cb_t cb) { if (cfg->lp_rtia >= LP_RTIA_COUNT) return 0; float rtia = lp_rtia_ohms[cfg->lp_rtia]; echem_init_lp(lp_rtia_map[cfg->lp_rtia]); /* set starting voltage and flush SINC2 filter */ AD5940_LPDAC0WriteS(mv_to_vbias_code(cfg->v_start), VZERO_CODE); vTaskDelay(pdMS_TO_TICKS(50)); for (int i = 0; i < 4; i++) read_current_ua(rtia); float v_range = cfg->v_stop - cfg->v_start; if (fabsf(v_range) < 0.001f) return 0; uint32_t n_steps; float step; lsv_calc_step(cfg, max_points, &n_steps, &step); float delay_ms = fabsf(step / cfg->scan_rate) * 1000.0f; if (delay_ms < 1.0f) delay_ms = 1.0f; TickType_t ticks = pdMS_TO_TICKS((uint32_t)delay_ms); if (ticks < 1) ticks = 1; printf("\n%10s %10s\n", "V(mV)", "I(uA)"); printf("------------------------\n"); for (uint32_t i = 0; i < n_steps; i++) { float v_mv = cfg->v_start + i * step; uint16_t code = mv_to_vbias_code(v_mv); AD5940_LPDAC0WriteS(code, VZERO_CODE); vTaskDelay(ticks); float i_ua = read_current_ua(rtia); out[i].v_mv = v_mv; out[i].i_ua = i_ua; printf("%10.1f %10.3f\n", v_mv, i_ua); if (cb) cb((uint16_t)i, v_mv, i_ua); } echem_shutdown_lp(); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); return (int)n_steps; } int echem_amp(const AmpConfig *cfg, AmpPoint *out, uint32_t max_points, amp_point_cb_t cb) { if (cfg->lp_rtia >= LP_RTIA_COUNT) return 0; float rtia = lp_rtia_ohms[cfg->lp_rtia]; echem_init_lp(lp_rtia_map[cfg->lp_rtia]); uint16_t code = mv_to_vbias_code(cfg->v_hold); AD5940_LPDAC0WriteS(code, VZERO_CODE); vTaskDelay(pdMS_TO_TICKS(50)); for (int i = 0; i < 4; i++) read_current_ua(rtia); TickType_t interval = pdMS_TO_TICKS((uint32_t)cfg->interval_ms); if (interval < 1) interval = 1; uint32_t max_samples = max_points; if (cfg->duration_s > 0) { uint32_t duration_n = (uint32_t)(cfg->duration_s * 1000.0f / cfg->interval_ms + 0.5f); if (duration_n < max_samples) max_samples = duration_n; } printf("\n%10s %10s\n", "t(ms)", "I(uA)"); printf("------------------------\n"); TickType_t t0 = xTaskGetTickCount(); uint32_t count = 0; for (uint32_t i = 0; i < max_samples; i++) { Command cmd; if (protocol_recv_command(&cmd, 0) == 0 && cmd.type == CMD_STOP_AMP) break; float i_ua = read_current_ua(rtia); float t_ms = (float)(xTaskGetTickCount() - t0) * portTICK_PERIOD_MS; out[i].t_ms = t_ms; out[i].i_ua = i_ua; count++; printf("%10.1f %10.3f\n", t_ms, i_ua); if (cb) cb((uint16_t)i, t_ms, i_ua); vTaskDelay(interval); } echem_shutdown_lp(); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); return (int)count; } void echem_default_cl(ClConfig *cfg) { memset(cfg, 0, sizeof(*cfg)); cfg->v_cond = 800.0f; /* +800 mV conditioning pulse */ cfg->t_cond_ms = 2000.0f; cfg->v_free = 100.0f; /* +100 mV for HOCl reduction */ cfg->v_total = -200.0f; /* -200 mV for total chlorine */ cfg->t_dep_ms = 5000.0f; /* 5s settling */ cfg->t_meas_ms = 5000.0f; /* 5s sampling */ cfg->lp_rtia = LP_RTIA_10K; } static uint32_t sample_phase(float v_mv, float t_dep_ms, float t_meas_ms, uint8_t phase, float rtia_ohms, ClPoint *out, uint32_t idx, uint32_t max_points, TickType_t t0, float *avg_out, cl_point_cb_t cb) { AD5940_LPDAC0WriteS(mv_to_vbias_code(v_mv), VZERO_CODE); /* settling — no samples recorded */ vTaskDelay(pdMS_TO_TICKS((uint32_t)t_dep_ms)); /* measurement — sample at ~50ms intervals */ uint32_t n_samples = (uint32_t)(t_meas_ms / 50.0f + 0.5f); if (n_samples < 2) n_samples = 2; TickType_t interval = pdMS_TO_TICKS(50); float sum = 0; uint32_t count = 0; for (uint32_t i = 0; i < n_samples && idx < max_points; i++) { float i_ua = read_current_ua(rtia_ohms); float t_ms = (float)(xTaskGetTickCount() - t0) * portTICK_PERIOD_MS; out[idx].t_ms = t_ms; out[idx].i_ua = i_ua; out[idx].phase = phase; if (cb) cb((uint16_t)idx, t_ms, i_ua, phase); idx++; sum += i_ua; count++; vTaskDelay(interval); } *avg_out = (count > 0) ? sum / (float)count : 0.0f; return idx; } int echem_chlorine(const ClConfig *cfg, ClPoint *out, uint32_t max_points, ClResult *result, cl_point_cb_t cb) { if (cfg->lp_rtia >= LP_RTIA_COUNT) return 0; float rtia = lp_rtia_ohms[cfg->lp_rtia]; echem_init_lp(lp_rtia_map[cfg->lp_rtia]); TickType_t t0 = xTaskGetTickCount(); uint32_t idx = 0; printf("Cl: conditioning at %.0f mV for %.0f ms\n", cfg->v_cond, cfg->t_cond_ms); AD5940_LPDAC0WriteS(mv_to_vbias_code(cfg->v_cond), VZERO_CODE); vTaskDelay(pdMS_TO_TICKS((uint32_t)cfg->t_cond_ms)); printf("Cl: free chlorine at %.0f mV\n", cfg->v_free); idx = sample_phase(cfg->v_free, cfg->t_dep_ms, cfg->t_meas_ms, CL_PHASE_FREE, rtia, out, idx, max_points, t0, &result->i_free_ua, cb); printf("Cl: total chlorine at %.0f mV\n", cfg->v_total); idx = sample_phase(cfg->v_total, cfg->t_dep_ms, cfg->t_meas_ms, CL_PHASE_TOTAL, rtia, out, idx, max_points, t0, &result->i_total_ua, cb); printf("Cl: free=%.3f uA, total=%.3f uA\n", result->i_free_ua, result->i_total_ua); echem_shutdown_lp(); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); return (int)idx; } void echem_default_ph(PhConfig *cfg) { memset(cfg, 0, sizeof(*cfg)); cfg->stabilize_s = 30.0f; cfg->temp_c = 25.0f; } int echem_ph_ocp(const PhConfig *cfg, PhResult *result) { echem_init_adc(); /* ADC mux: read SE0 and RE0 pin voltages directly */ ADCBaseCfg_Type adc; adc.ADCMuxP = ADCMUXP_VSE0; adc.ADCMuxN = ADCMUXN_VSET1P1; adc.ADCPga = ADCPGA_1P5; AD5940_ADCBaseCfgS(&adc); printf("pH: stabilizing %0.f s\n", cfg->stabilize_s); vTaskDelay(pdMS_TO_TICKS((uint32_t)(cfg->stabilize_s * 1000.0f))); /* average N readings of V(SE0) and V(RE0) */ #define PH_AVG_N 10 float sum_se0 = 0, sum_re0 = 0; for (int i = 0; i < PH_AVG_N; i++) { sum_se0 += read_voltage_mv(ADCMUXP_VSE0); sum_re0 += read_voltage_mv(ADCMUXP_VRE0); vTaskDelay(pdMS_TO_TICKS(100)); } float v_se0 = sum_se0 / PH_AVG_N; float v_re0 = sum_re0 / PH_AVG_N; float ocp = v_se0 - v_re0; float t_k = cfg->temp_c + 273.15f; float slope = 0.1984f * t_k; /* mV/pH at temperature */ result->v_ocp_mv = ocp; result->ph = 7.0f - ocp / slope; result->temp_c = cfg->temp_c; printf("pH: SE0=%.1f mV, RE0=%.1f mV, OCP=%.1f mV, pH=%.2f\n", v_se0, v_re0, ocp, result->ph); echem_shutdown_lp(); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); return 0; }