recovered working tree: paired DFT, ratiometric Z, open-cal, BLE event refactor

This commit is contained in:
jess 2026-03-30 18:28:57 -07:00
parent 5268d55b6f
commit 5ae607eec4
7 changed files with 238 additions and 80 deletions

View File

@ -8,6 +8,7 @@ use std::fmt::Write;
use std::time::Duration; use std::time::Duration;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::ble::BleEvent;
use crate::native_menu::{MenuAction, NativeMenu}; use crate::native_menu::{MenuAction, NativeMenu};
use crate::protocol::{ use crate::protocol::{
self, AmpPoint, ClPoint, ClResult, Electrode, EisMessage, EisPoint, LpRtia, LsvPoint, self, AmpPoint, ClPoint, ClResult, Electrode, EisMessage, EisPoint, LpRtia, LsvPoint,
@ -732,11 +733,30 @@ impl App {
self.midi_gen, self.midi_gen,
iced::stream::channel(100, |mut output| async move { iced::stream::channel(100, |mut output| async move {
loop { loop {
let _ = output.send(Message::BleStatus("Looking for MIDI device...".into())).await; let (ble_tx, mut ble_rx) = mpsc::unbounded_channel::<BleEvent>();
match crate::ble::connect_and_stream(&mut output).await { let (cmd_tx, cmd_rx) = mpsc::unbounded_channel::<Vec<u8>>();
Ok(()) => eprintln!("BLE: session ended cleanly"),
Err(e) => eprintln!("BLE: session error: {e}"), let tx = ble_tx.clone();
tokio::spawn(async move {
if let Err(e) = crate::ble::connect_and_run(tx, cmd_rx).await {
eprintln!("BLE: {e}");
} }
});
let mut ready_sent = false;
while let Some(ev) = ble_rx.recv().await {
let msg = match ev {
BleEvent::Status(ref s) if s == "Connected" && !ready_sent => {
ready_sent = true;
let _ = output.send(Message::BleReady(cmd_tx.clone())).await;
Message::BleStatus(s.clone())
}
BleEvent::Status(s) => Message::BleStatus(s),
BleEvent::Data(m) => Message::BleData(m),
};
let _ = output.send(msg).await;
}
let _ = output.send(Message::BleStatus("Reconnecting...".into())).await; let _ = output.send(Message::BleStatus("Reconnecting...".into())).await;
tokio::time::sleep(Duration::from_millis(500)).await; tokio::time::sleep(Duration::from_millis(500)).await;
} }

View File

@ -1,29 +1,31 @@
use futures::SinkExt;
use midir::{MidiInput, MidiOutput, MidiInputConnection, MidiOutputConnection}; use midir::{MidiInput, MidiOutput, MidiInputConnection, MidiOutputConnection};
use std::sync::mpsc as std_mpsc; use std::sync::mpsc as std_mpsc;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::app::Message; use crate::protocol::{self, EisMessage};
use crate::protocol;
const DEVICE_NAME: &str = "EIS4"; const DEVICE_NAME: &str = "EIS4";
pub async fn connect_and_stream( #[derive(Debug, Clone)]
output: &mut futures::channel::mpsc::Sender<Message>, pub enum BleEvent {
Status(String),
Data(EisMessage),
}
pub async fn connect_and_run(
tx: mpsc::UnboundedSender<BleEvent>,
mut cmd_rx: mpsc::UnboundedReceiver<Vec<u8>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
eprintln!("BLE: scanning for MIDI device '{DEVICE_NAME}'..."); let _ = tx.send(BleEvent::Status("Looking for MIDI device...".into()));
let (midi_in, in_port, midi_out, out_port) = loop { let (midi_in, in_port, midi_out, out_port) = loop {
match find_midi_ports() { if let Some(found) = find_midi_ports() {
Some(found) => break found, break found;
None => { }
tokio::time::sleep(std::time::Duration::from_millis(500)).await; tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
}
}; };
eprintln!("BLE: found ports, connecting..."); let _ = tx.send(BleEvent::Status("Connecting MIDI...".into()));
let _ = output.send(Message::BleStatus("Connecting MIDI...".into())).await;
let (sysex_tx, sysex_rx) = std_mpsc::channel::<Vec<u8>>(); let (sysex_tx, sysex_rx) = std_mpsc::channel::<Vec<u8>>();
@ -41,16 +43,12 @@ pub async fn connect_and_stream(
&out_port, "cue-out", &out_port, "cue-out",
).map_err(|e| format!("MIDI output connect: {e}"))?; ).map_err(|e| format!("MIDI output connect: {e}"))?;
eprintln!("BLE: connected"); let _ = tx.send(BleEvent::Status("Connected".into()));
let _ = output.send(Message::BleStatus("Connected".into())).await;
let (cmd_tx, mut cmd_rx) = mpsc::unbounded_channel::<Vec<u8>>();
let _ = output.send(Message::BleReady(cmd_tx)).await;
loop { loop {
while let Ok(sysex) = sysex_rx.try_recv() { while let Ok(sysex) = sysex_rx.try_recv() {
if let Some(msg) = protocol::parse_sysex(&sysex) { if let Some(msg) = protocol::parse_sysex(&sysex) {
let _ = output.send(Message::BleData(msg)).await; let _ = tx.send(BleEvent::Data(msg));
} }
} }
@ -58,14 +56,10 @@ pub async fn connect_and_stream(
match cmd_rx.try_recv() { match cmd_rx.try_recv() {
Ok(pkt) => { Ok(pkt) => {
if let Err(e) = out_conn.send(&pkt) { if let Err(e) = out_conn.send(&pkt) {
eprintln!("BLE: MIDI send error: {e}"); eprintln!("MIDI send error: {e}");
return Err(e.into());
} }
} }
Err(mpsc::error::TryRecvError::Disconnected) => { Err(mpsc::error::TryRecvError::Disconnected) => return Ok(()),
eprintln!("BLE: cmd channel closed");
return Ok(());
}
Err(mpsc::error::TryRecvError::Empty) => break, Err(mpsc::error::TryRecvError::Empty) => break,
} }
} }
@ -81,25 +75,11 @@ fn find_midi_ports() -> Option<(
let midi_in = MidiInput::new("cue-in").ok()?; let midi_in = MidiInput::new("cue-in").ok()?;
let midi_out = MidiOutput::new("cue-out").ok()?; let midi_out = MidiOutput::new("cue-out").ok()?;
let in_ports = midi_in.ports(); let in_port = midi_in.ports().into_iter().find(|p| {
let out_ports = midi_out.ports();
let in_names: Vec<_> = in_ports.iter()
.filter_map(|p| midi_in.port_name(p).ok())
.collect();
let out_names: Vec<_> = out_ports.iter()
.filter_map(|p| midi_out.port_name(p).ok())
.collect();
if !in_names.is_empty() || !out_names.is_empty() {
eprintln!("BLE: MIDI ports — in: {:?}, out: {:?}", in_names, out_names);
}
let in_port = in_ports.into_iter().find(|p| {
midi_in.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME)) midi_in.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME))
})?; })?;
let out_port = out_ports.into_iter().find(|p| { let out_port = midi_out.ports().into_iter().find(|p| {
midi_out.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME)) midi_out.port_name(p).map_or(false, |n| n.contains(DEVICE_NAME))
})?; })?;

View File

@ -185,6 +185,8 @@ static void parse_one_sysex(const uint8_t *midi, uint16_t mlen)
case CMD_START_REFS: case CMD_START_REFS:
case CMD_GET_REFS: case CMD_GET_REFS:
case CMD_CLEAR_REFS: case CMD_CLEAR_REFS:
case CMD_OPEN_CAL:
case CMD_CLEAR_OPEN_CAL:
break; break;
default: default:
return; return;

View File

@ -18,6 +18,8 @@
#define CMD_START_CL 0x23 #define CMD_START_CL 0x23
#define CMD_START_PH 0x24 #define CMD_START_PH 0x24
#define CMD_START_CLEAN 0x25 #define CMD_START_CLEAN 0x25
#define CMD_OPEN_CAL 0x26
#define CMD_CLEAR_OPEN_CAL 0x27
#define CMD_START_REFS 0x30 #define CMD_START_REFS 0x30
#define CMD_GET_REFS 0x31 #define CMD_GET_REFS 0x31
#define CMD_CLEAR_REFS 0x32 #define CMD_CLEAR_REFS 0x32

View File

@ -4,6 +4,8 @@
#include <stdio.h> #include <stdio.h>
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "nvs_flash.h"
#include "nvs.h"
#ifndef M_PI #ifndef M_PI
#define M_PI 3.14159265358979323846 #define M_PI 3.14159265358979323846
@ -21,6 +23,14 @@ static struct {
uint32_t dertia_reg; uint32_t dertia_reg;
} ctx; } ctx;
/* open-circuit calibration data */
static struct {
fImpCar_Type y[EIS_MAX_POINTS]; /* admittance at each freq */
float freq[EIS_MAX_POINTS];
uint32_t n;
int valid;
} ocal;
static const uint32_t rtia_map[] = { static const uint32_t rtia_map[] = {
[RTIA_200] = HSTIARTIA_200, [RTIA_200] = HSTIARTIA_200,
[RTIA_1K] = HSTIARTIA_1K, [RTIA_1K] = HSTIARTIA_1K,
@ -127,9 +137,13 @@ void eis_default_config(EISConfig *cfg)
uint32_t eis_calc_num_points(const EISConfig *cfg) uint32_t eis_calc_num_points(const EISConfig *cfg)
{ {
if (cfg->freq_stop_hz <= cfg->freq_start_hz || cfg->points_per_decade == 0) if (cfg->points_per_decade == 0)
return 1; return 1;
float decades = log10f(cfg->freq_stop_hz / cfg->freq_start_hz); if (cfg->freq_start_hz == cfg->freq_stop_hz)
return 24; /* fixed-freq repeatability test */
float lo = fminf(cfg->freq_start_hz, cfg->freq_stop_hz);
float hi = fmaxf(cfg->freq_start_hz, cfg->freq_stop_hz);
float decades = log10f(hi / lo);
uint32_t n = (uint32_t)(decades * cfg->points_per_decade + 0.5f) + 1; uint32_t n = (uint32_t)(decades * cfg->points_per_decade + 0.5f) + 1;
if (n > EIS_MAX_POINTS) n = EIS_MAX_POINTS; if (n > EIS_MAX_POINTS) n = EIS_MAX_POINTS;
if (n < 2) n = 2; if (n < 2) n = 2;
@ -164,7 +178,7 @@ void eis_init(const EISConfig *cfg)
ref.LpRefBufEn = bFALSE; ref.LpRefBufEn = bFALSE;
AD5940_REFCfgS(&ref); AD5940_REFCfgS(&ref);
AD5940_AFEPwrBW(AFEPWR_LP, AFEBW_250KHZ); AD5940_AFEPwrBW(AFEPWR_HP, AFEBW_250KHZ);
AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DFTRDY, bTRUE); AD5940_INTCCfg(AFEINTC_0, AFEINTSRC_DFTRDY, bTRUE);
AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_DFTRDY, bTRUE); AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_DFTRDY, bTRUE);
@ -214,7 +228,7 @@ static void configure_freq(float freq_hz)
fp.DftSrc = DFTSRC_ADCRAW; fp.DftSrc = DFTSRC_ADCRAW;
fp.ADCSinc3Osr = ADCSINC3OSR_2; fp.ADCSinc3Osr = ADCSINC3OSR_2;
fp.ADCSinc2Osr = 0; fp.ADCSinc2Osr = 0;
fp.DftNum = DFTNUM_16384; fp.DftNum = DFTNUM_4096;
} }
AD5940_WriteReg(REG_AFE_WGFCW, AD5940_WriteReg(REG_AFE_WGFCW,
@ -247,7 +261,10 @@ static int32_t sign_extend_18(uint32_t v)
return (v & (1UL << 17)) ? (int32_t)(v | 0xFFFC0000UL) : (int32_t)v; return (v & (1UL << 17)) ? (int32_t)(v | 0xFFFC0000UL) : (int32_t)v;
} }
static void dft_measure(uint32_t mux_p, uint32_t mux_n, iImpCar_Type *out) /* paired DFT: two measurements under continuous WG excitation */
static void dft_measure_pair(
uint32_t mux1_p, uint32_t mux1_n, iImpCar_Type *out1,
uint32_t mux2_p, uint32_t mux2_n, iImpCar_Type *out2)
{ {
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bFALSE); AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bFALSE);
AD5940_WriteReg(REG_AFE_FIFOCON, 0); AD5940_WriteReg(REG_AFE_FIFOCON, 0);
@ -255,32 +272,48 @@ static void dft_measure(uint32_t mux_p, uint32_t mux_n, iImpCar_Type *out)
AD5940_ReadAfeResult(AFERESULT_DFTIMAGE); AD5940_ReadAfeResult(AFERESULT_DFTIMAGE);
AD5940_INTCClrFlag(AFEINTSRC_DFTRDY); AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);
AD5940_ADCMuxCfgS(mux_p, mux_n); AD5940_ADCMuxCfgS(mux1_p, mux1_n);
AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR, bTRUE); AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR, bTRUE);
AD5940_Delay10us(25); AD5940_Delay10us(25);
AD5940_ClrMCUIntFlag(); AD5940_ClrMCUIntFlag();
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE); AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
while (!AD5940_GetMCUIntFlag()) while (!AD5940_GetMCUIntFlag())
vTaskDelay(1); vTaskDelay(1);
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);
out1->Real = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTREAL));
out1->Image = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTIMAGE));
out1->Image = -out1->Image;
/* switch ADC mux, flush stale pipeline, short settle */
AD5940_ADCMuxCfgS(mux2_p, mux2_n);
AD5940_ReadAfeResult(AFERESULT_DFTREAL);
AD5940_ReadAfeResult(AFERESULT_DFTIMAGE);
AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);
AD5940_Delay10us(5);
AD5940_ClrMCUIntFlag();
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
while (!AD5940_GetMCUIntFlag())
vTaskDelay(1);
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT | AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT |
AFECTRL_WG | AFECTRL_ADCPWR, bFALSE); AFECTRL_WG | AFECTRL_ADCPWR, bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_DFTRDY); AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);
out->Real = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTREAL)); out2->Real = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTREAL));
out->Image = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTIMAGE)); out2->Image = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTIMAGE));
out->Image = -out->Image; out2->Image = -out2->Image;
} }
/* RTIA calibration: 2 DFTs through current RCAL switch config */ static fImpCar_Type measure_rtia(iImpCar_Type *out_hstia)
static fImpCar_Type measure_rtia(void)
{ {
iImpCar_Type v_rcal, v_raw; iImpCar_Type v_rcal, v_raw;
dft_measure(ADCMUXP_P_NODE, ADCMUXN_N_NODE, &v_rcal); dft_measure_pair(
dft_measure(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N, &v_raw); ADCMUXP_P_NODE, ADCMUXN_N_NODE, &v_rcal,
ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N, &v_raw);
if (out_hstia) *out_hstia = v_raw;
v_raw.Real = -v_raw.Real; v_raw.Real = -v_raw.Real;
v_raw.Image = -v_raw.Image; v_raw.Image = -v_raw.Image;
fImpCar_Type rtia = AD5940_ComplexDivInt(&v_raw, &v_rcal); fImpCar_Type rtia = AD5940_ComplexDivInt(&v_raw, &v_rcal);
@ -309,8 +342,9 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AFECTRL_EXTBUFPWR | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR | AFECTRL_EXTBUFPWR | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR |
AFECTRL_SINC2NOTCH, bTRUE); AFECTRL_SINC2NOTCH, bTRUE);
/* RCAL before */ /* RCAL before — capture raw HSTIA DFT for ratiometric diagnostic */
fImpCar_Type rtia_before = measure_rtia(); iImpCar_Type rcal_hstia;
fImpCar_Type rtia_before = measure_rtia(&rcal_hstia);
/* DUT forward */ /* DUT forward */
sw.Dswitch = ctx.dut_sw_d; sw.Dswitch = ctx.dut_sw_d;
@ -320,10 +354,11 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AD5940_SWMatrixCfgS(&sw); AD5940_SWMatrixCfgS(&sw);
AD5940_Delay10us(50); AD5940_Delay10us(50);
dft_measure(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N, &v_tia); dft_measure_pair(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N, &v_tia,
ctx.dut_mux_vp, ctx.dut_mux_vn, &v_sense);
iImpCar_Type dut_hstia_raw = v_tia;
v_tia.Real = -v_tia.Real; v_tia.Real = -v_tia.Real;
v_tia.Image = -v_tia.Image; v_tia.Image = -v_tia.Image;
dft_measure(ctx.dut_mux_vp, ctx.dut_mux_vn, &v_sense);
iImpCar_Type v_tia_fwd = v_tia; iImpCar_Type v_tia_fwd = v_tia;
iImpCar_Type v_sense_fwd = v_sense; iImpCar_Type v_sense_fwd = v_sense;
@ -336,7 +371,7 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AD5940_SWMatrixCfgS(&sw); AD5940_SWMatrixCfgS(&sw);
AD5940_Delay10us(50); AD5940_Delay10us(50);
fImpCar_Type rtia_after = measure_rtia(); fImpCar_Type rtia_after = measure_rtia(NULL);
/* DUT reverse (DUT first, then RCAL) */ /* DUT reverse (DUT first, then RCAL) */
sw.Dswitch = ctx.dut_sw_d; sw.Dswitch = ctx.dut_sw_d;
@ -346,10 +381,10 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AD5940_SWMatrixCfgS(&sw); AD5940_SWMatrixCfgS(&sw);
AD5940_Delay10us(50); AD5940_Delay10us(50);
dft_measure(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N, &v_tia); dft_measure_pair(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N, &v_tia,
ctx.dut_mux_vp, ctx.dut_mux_vn, &v_sense);
v_tia.Real = -v_tia.Real; v_tia.Real = -v_tia.Real;
v_tia.Image = -v_tia.Image; v_tia.Image = -v_tia.Image;
dft_measure(ctx.dut_mux_vp, ctx.dut_mux_vn, &v_sense);
iImpCar_Type v_tia_rev = v_tia; iImpCar_Type v_tia_rev = v_tia;
iImpCar_Type v_sense_rev = v_sense; iImpCar_Type v_sense_rev = v_sense;
@ -362,7 +397,7 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AD5940_SWMatrixCfgS(&sw); AD5940_SWMatrixCfgS(&sw);
AD5940_Delay10us(50); AD5940_Delay10us(50);
fImpCar_Type rtia_rev = measure_rtia(); fImpCar_Type rtia_rev = measure_rtia(NULL);
/* power down, open switches */ /* power down, open switches */
AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR | AFECTRL_ADCCNV | AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR | AFECTRL_ADCCNV |
@ -391,9 +426,29 @@ int eis_measure_point(float freq_hz, EISPoint *out)
fImpCar_Type ft_rev = { (float)v_tia_rev.Real, (float)v_tia_rev.Image }; fImpCar_Type ft_rev = { (float)v_tia_rev.Real, (float)v_tia_rev.Image };
num = AD5940_ComplexMulFloat(&fs_rev, &rtia_rev); num = AD5940_ComplexMulFloat(&fs_rev, &rtia_rev);
fImpCar_Type z_rev = AD5940_ComplexDivFloat(&num, &ft_rev); fImpCar_Type z_rev = AD5940_ComplexDivFloat(&num, &ft_rev);
(void)z_rev;
/* HSTIA-only ratiometric: Z = (DftRcal / DftDut) * RCAL */
fImpCar_Type fr = { (float)rcal_hstia.Real, (float)rcal_hstia.Image };
fImpCar_Type fd = { (float)dut_hstia_raw.Real, (float)dut_hstia_raw.Image };
fImpCar_Type z_ratio = AD5940_ComplexDivFloat(&fr, &fd);
z_ratio.Real *= ctx.rcal_ohms;
z_ratio.Image *= ctx.rcal_ohms;
/* apply open-circuit compensation if available */
if (ocal.valid) {
for (uint32_t k = 0; k < ocal.n; k++) {
if (fabsf(ocal.freq[k] - freq_hz) < freq_hz * 0.01f) {
fImpCar_Type one = {1.0f, 0.0f};
fImpCar_Type y_meas = AD5940_ComplexDivFloat(&one, &z_fwd);
fImpCar_Type y_corr = AD5940_ComplexSubFloat(&y_meas, &ocal.y[k]);
z_fwd = AD5940_ComplexDivFloat(&one, &y_corr);
break;
}
}
}
float mag_fwd = AD5940_ComplexMag(&z_fwd); float mag_fwd = AD5940_ComplexMag(&z_fwd);
float mag_rev = AD5940_ComplexMag(&z_rev);
out->freq_hz = freq_hz; out->freq_hz = freq_hz;
out->z_real = z_fwd.Real; out->z_real = z_fwd.Real;
@ -402,10 +457,9 @@ int eis_measure_point(float freq_hz, EISPoint *out)
out->phase_deg = AD5940_ComplexPhase(&z_fwd) * (float)(180.0 / M_PI); out->phase_deg = AD5940_ComplexPhase(&z_fwd) * (float)(180.0 / M_PI);
out->rtia_mag_before = AD5940_ComplexMag(&rtia_before); out->rtia_mag_before = AD5940_ComplexMag(&rtia_before);
out->rtia_mag_after = AD5940_ComplexMag(&rtia_after); out->rtia_mag_after = AD5940_ComplexMag(&rtia_after);
out->rev_mag = mag_rev; out->rev_mag = AD5940_ComplexMag(&z_ratio);
out->rev_phase = AD5940_ComplexPhase(&z_rev) * (float)(180.0 / M_PI); out->rev_phase = AD5940_ComplexPhase(&z_ratio) * (float)(180.0 / M_PI);
out->pct_err = (mag_fwd > 0.0f) out->pct_err = 0.0f;
? fabsf(mag_fwd - mag_rev) / mag_fwd * 100.0f : 0.0f;
return 0; return 0;
} }
@ -427,23 +481,33 @@ int eis_sweep(EISPoint *out, uint32_t max_points, eis_point_cb_t cb)
sweep.SweepLog = bTRUE; sweep.SweepLog = bTRUE;
sweep.SweepIndex = 0; sweep.SweepIndex = 0;
printf("\n%10s %12s %10s %12s %12s %7s\n", printf("\n%10s %12s %10s %12s %12s | %12s %10s %6s\n",
"Freq(Hz)", "|Z|(Ohm)", "Phase(deg)", "Re(Ohm)", "Im(Ohm)", "Err%"); "Freq(Hz)", "|Z|dual", "Ph_dual", "Re_dual", "Im_dual",
printf("---------------------------------------------------------------------\n"); "|Z|ratio", "Ph_ratio", "ms");
printf("--------------------------------------------------------------------------"
"-------------------------\n");
uint32_t t0 = xTaskGetTickCount();
eis_measure_point(ctx.cfg.freq_start_hz, &out[0]); eis_measure_point(ctx.cfg.freq_start_hz, &out[0]);
printf("%10.1f %12.2f %10.2f %12.2f %12.2f %6.2f%%\n", uint32_t t1 = xTaskGetTickCount();
printf("%10.1f %12.2f %10.2f %12.2f %12.2f | %12.2f %10.2f %6lu\n",
out[0].freq_hz, out[0].mag_ohms, out[0].phase_deg, out[0].freq_hz, out[0].mag_ohms, out[0].phase_deg,
out[0].z_real, out[0].z_imag, out[0].pct_err); out[0].z_real, out[0].z_imag,
out[0].rev_mag, out[0].rev_phase,
(unsigned long)((t1 - t0) * portTICK_PERIOD_MS));
if (cb) cb(0, &out[0]); if (cb) cb(0, &out[0]);
for (uint32_t i = 1; i < n; i++) { for (uint32_t i = 1; i < n; i++) {
float freq; float freq;
AD5940_SweepNext(&sweep, &freq); AD5940_SweepNext(&sweep, &freq);
t0 = xTaskGetTickCount();
eis_measure_point(freq, &out[i]); eis_measure_point(freq, &out[i]);
printf("%10.1f %12.2f %10.2f %12.2f %12.2f %6.2f%%\n", t1 = xTaskGetTickCount();
printf("%10.1f %12.2f %10.2f %12.2f %12.2f | %12.2f %10.2f %6lu\n",
out[i].freq_hz, out[i].mag_ohms, out[i].phase_deg, out[i].freq_hz, out[i].mag_ohms, out[i].phase_deg,
out[i].z_real, out[i].z_imag, out[i].pct_err); out[i].z_real, out[i].z_imag,
out[i].rev_mag, out[i].rev_phase,
(unsigned long)((t1 - t0) * portTICK_PERIOD_MS));
if (cb) cb((uint16_t)i, &out[i]); if (cb) cb((uint16_t)i, &out[i]);
} }
@ -453,3 +517,71 @@ int eis_sweep(EISPoint *out, uint32_t max_points, eis_point_cb_t cb)
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE); AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
return (int)n; return (int)n;
} }
#define NVS_OCAL_NS "eis"
#define NVS_OCAL_KEY "ocal"
static void ocal_save_nvs(void)
{
nvs_handle_t h;
if (nvs_open(NVS_OCAL_NS, NVS_READWRITE, &h) != ESP_OK) return;
nvs_set_blob(h, NVS_OCAL_KEY, &ocal, sizeof(ocal));
nvs_commit(h);
nvs_close(h);
}
static void ocal_erase_nvs(void)
{
nvs_handle_t h;
if (nvs_open(NVS_OCAL_NS, NVS_READWRITE, &h) != ESP_OK) return;
nvs_erase_key(h, NVS_OCAL_KEY);
nvs_commit(h);
nvs_close(h);
}
void eis_load_open_cal(void)
{
nvs_handle_t h;
if (nvs_open(NVS_OCAL_NS, NVS_READONLY, &h) != ESP_OK) return;
size_t len = sizeof(ocal);
if (nvs_get_blob(h, NVS_OCAL_KEY, &ocal, &len) == ESP_OK && len == sizeof(ocal) && ocal.valid) {
printf("Open-circuit cal loaded from NVS: %u points\n", (unsigned)ocal.n);
} else {
ocal.valid = 0;
ocal.n = 0;
}
nvs_close(h);
}
int eis_open_cal(EISPoint *buf, uint32_t max_points, eis_point_cb_t cb)
{
ocal.valid = 0;
ocal.n = 0;
int n = eis_sweep(buf, max_points, cb);
if (n <= 0) return n;
fImpCar_Type one = {1.0f, 0.0f};
for (int i = 0; i < n && i < EIS_MAX_POINTS; i++) {
fImpCar_Type z = { buf[i].z_real, buf[i].z_imag };
ocal.y[i] = AD5940_ComplexDivFloat(&one, &z);
ocal.freq[i] = buf[i].freq_hz;
}
ocal.n = (uint32_t)n;
ocal.valid = 1;
ocal_save_nvs();
printf("Open-circuit cal stored: %d points\n", n);
return n;
}
void eis_clear_open_cal(void)
{
ocal.valid = 0;
ocal.n = 0;
ocal_erase_nvs();
}
int eis_has_open_cal(void)
{
return ocal.valid;
}

View File

@ -58,4 +58,9 @@ int eis_measure_point(float freq_hz, EISPoint *out);
int eis_sweep(EISPoint *out, uint32_t max_points, eis_point_cb_t cb); int eis_sweep(EISPoint *out, uint32_t max_points, eis_point_cb_t cb);
uint32_t eis_calc_num_points(const EISConfig *cfg); uint32_t eis_calc_num_points(const EISConfig *cfg);
int eis_open_cal(EISPoint *buf, uint32_t max_points, eis_point_cb_t cb);
void eis_clear_open_cal(void);
int eis_has_open_cal(void);
void eis_load_open_cal(void);
#endif #endif

View File

@ -52,6 +52,7 @@ void app_main(void)
if (adiid != AD5941_EXPECTED_ADIID) return; if (adiid != AD5941_EXPECTED_ADIID) return;
eis_default_config(&cfg); eis_default_config(&cfg);
eis_load_open_cal();
temp_init(); temp_init();
esp_log_level_set("NimBLE", ESP_LOG_WARN); esp_log_level_set("NimBLE", ESP_LOG_WARN);
@ -183,6 +184,22 @@ void app_main(void)
printf("Refs cleared\n"); printf("Refs cleared\n");
break; 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: { case CMD_START_CL: {
ClConfig cl_cfg; ClConfig cl_cfg;
cl_cfg.v_cond = cmd.cl.v_cond; cl_cfg.v_cond = cmd.cl.v_cond;