#include #include "ad5940.h" #include "ad5941_port.h" #include "eis.h" #include "echem.h" #include "ble.h" #include "temp.h" #include "refs.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "nvs_flash.h" #include "esp_log.h" #define AD5941_EXPECTED_ADIID 0x4144 static EISConfig cfg; static EISPoint results[EIS_MAX_POINTS]; static LSVPoint lsv_results[ECHEM_MAX_POINTS]; static AmpPoint amp_results[ECHEM_MAX_POINTS]; static ClPoint cl_results[ECHEM_MAX_POINTS]; static RefStore ref_store; static void do_sweep(void) { eis_init(&cfg); uint32_t n = eis_calc_num_points(&cfg); ble_send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz); int got = eis_sweep(results, n, ble_send_eis_point); printf("Sweep complete: %d points\n", got); ble_send_sweep_end(); } void app_main(void) { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { nvs_flash_erase(); nvs_flash_init(); } printf("EIS4: AD5941 bring-up\n"); AD5940_MCUResourceInit(NULL); vTaskDelay(pdMS_TO_TICKS(50)); AD5940_HWReset(); vTaskDelay(pdMS_TO_TICKS(200)); AD5940_Initialize(); uint32_t adiid = AD5940_ReadReg(REG_AFECON_ADIID); printf("ADIID: 0x%04lX %s\n", adiid, adiid == AD5941_EXPECTED_ADIID ? "(OK)" : "(FAIL)"); if (adiid != AD5941_EXPECTED_ADIID) return; eis_default_config(&cfg); eis_load_open_cal(); temp_init(); esp_log_level_set("NimBLE", ESP_LOG_WARN); ble_init(); printf("Waiting for BLE connection...\n"); ble_wait_for_connection(); ble_send_config(&cfg); BleCommand cmd; for (;;) { if (ble_recv_command(&cmd, UINT32_MAX) != 0) continue; switch (cmd.type) { case CMD_SET_SWEEP: cfg.freq_start_hz = cmd.sweep.freq_start; cfg.freq_stop_hz = cmd.sweep.freq_stop; cfg.points_per_decade = cmd.sweep.ppd; eis_reconfigure(&cfg); printf("Sweep: %.0f-%.0f Hz, %u ppd\n", cfg.freq_start_hz, cfg.freq_stop_hz, cfg.points_per_decade); break; case CMD_SET_RTIA: if (cmd.rtia < RTIA_COUNT) { cfg.rtia = cmd.rtia; eis_reconfigure(&cfg); printf("RTIA: %u\n", cfg.rtia); } break; case CMD_SET_RCAL: if (cmd.rcal < RCAL_COUNT) { cfg.rcal = cmd.rcal; eis_reconfigure(&cfg); printf("RCAL: %u\n", cfg.rcal); } break; case CMD_SET_ELECTRODE: if (cmd.electrode < ELEC_COUNT) { cfg.electrode = cmd.electrode; eis_reconfigure(&cfg); printf("Electrode: %s\n", cfg.electrode == ELEC_3WIRE ? "3-wire (CE0/RE0/SE0)" : "4-wire (AIN)"); } break; case CMD_START_SWEEP: printf("Config: %.0f-%.0f Hz, %u ppd, rtia=%u, rcal=%u, elec=%s\n", cfg.freq_start_hz, cfg.freq_stop_hz, cfg.points_per_decade, cfg.rtia, cfg.rcal, cfg.electrode == ELEC_3WIRE ? "3-wire" : "4-wire"); do_sweep(); break; case CMD_GET_CONFIG: ble_send_config(&cfg); break; case CMD_START_LSV: { LSVConfig lsv_cfg; lsv_cfg.v_start = cmd.lsv.v_start; lsv_cfg.v_stop = cmd.lsv.v_stop; lsv_cfg.scan_rate = cmd.lsv.scan_rate; lsv_cfg.lp_rtia = cmd.lsv.lp_rtia; printf("LSV: %.0f-%.0f mV, %.0f mV/s, rtia=%u\n", lsv_cfg.v_start, lsv_cfg.v_stop, lsv_cfg.scan_rate, lsv_cfg.lp_rtia); uint32_t n = echem_lsv_calc_steps(&lsv_cfg, ECHEM_MAX_POINTS); ble_send_lsv_start(n, lsv_cfg.v_start, lsv_cfg.v_stop); int got = echem_lsv(&lsv_cfg, lsv_results, ECHEM_MAX_POINTS, ble_send_lsv_point); printf("LSV complete: %d points\n", got); ble_send_lsv_end(); break; } case CMD_START_AMP: { AmpConfig amp_cfg; amp_cfg.v_hold = cmd.amp.v_hold; amp_cfg.interval_ms = cmd.amp.interval_ms; amp_cfg.duration_s = cmd.amp.duration_s; amp_cfg.lp_rtia = cmd.amp.lp_rtia; printf("Amp: %.0f mV, %.0f ms interval, %.0f s\n", amp_cfg.v_hold, amp_cfg.interval_ms, amp_cfg.duration_s); ble_send_amp_start(amp_cfg.v_hold); int got = echem_amp(&_cfg, amp_results, ECHEM_MAX_POINTS, ble_send_amp_point); printf("Amp complete: %d points\n", got); ble_send_amp_end(); break; } case CMD_GET_TEMP: ble_send_temp(temp_get()); break; case CMD_START_PH: { PhConfig ph_cfg; ph_cfg.stabilize_s = cmd.ph.stabilize_s; ph_cfg.temp_c = temp_get(); printf("pH: stabilize %.0f s, temp %.1f C\n", ph_cfg.stabilize_s, ph_cfg.temp_c); PhResult ph_result; echem_ph_ocp(&ph_cfg, &ph_result); printf("pH: OCP=%.1f mV, pH=%.2f\n", ph_result.v_ocp_mv, ph_result.ph); ble_send_ph_result(ph_result.v_ocp_mv, ph_result.ph, ph_result.temp_c); break; } case CMD_START_CLEAN: printf("Clean: %.0f mV, %.0f s\n", cmd.clean.v_mv, cmd.clean.duration_s); echem_clean(cmd.clean.v_mv, cmd.clean.duration_s); break; case CMD_START_REFS: printf("Ref collection starting\n"); refs_collect(&ref_store, &cfg); break; case CMD_GET_REFS: refs_send(&ref_store); break; case CMD_CLEAR_REFS: refs_clear(&ref_store); printf("Refs cleared\n"); break; case CMD_OPEN_CAL: { printf("Open-circuit cal starting\n"); eis_init(&cfg); uint32_t n = eis_calc_num_points(&cfg); ble_send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz); int got = eis_open_cal(results, n, ble_send_eis_point); printf("Open-circuit cal: %d points\n", got); ble_send_sweep_end(); break; } case CMD_CLEAR_OPEN_CAL: eis_clear_open_cal(); printf("Open-circuit cal cleared\n"); break; case CMD_START_CL: { ClConfig cl_cfg; cl_cfg.v_cond = cmd.cl.v_cond; cl_cfg.t_cond_ms = cmd.cl.t_cond_ms; cl_cfg.v_free = cmd.cl.v_free; cl_cfg.v_total = cmd.cl.v_total; cl_cfg.t_dep_ms = cmd.cl.t_dep_ms; cl_cfg.t_meas_ms = cmd.cl.t_meas_ms; cl_cfg.lp_rtia = cmd.cl.lp_rtia; uint32_t n_per = (uint32_t)(cl_cfg.t_meas_ms / 50.0f + 0.5f); if (n_per < 2) n_per = 2; ble_send_cl_start(2 * n_per); ClResult cl_result; int got = echem_chlorine(&cl_cfg, cl_results, ECHEM_MAX_POINTS, &cl_result, ble_send_cl_point); printf("Cl complete: %d points, free=%.3f uA, total=%.3f uA\n", got, cl_result.i_free_ua, cl_result.i_total_ua); ble_send_cl_result(cl_result.i_free_ua, cl_result.i_total_ua); ble_send_cl_end(); break; } } } }