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 tokio::sync::mpsc;
use crate::ble::BleEvent;
use crate::native_menu::{MenuAction, NativeMenu};
use crate::protocol::{
self, AmpPoint, ClPoint, ClResult, Electrode, EisMessage, EisPoint, LpRtia, LsvPoint,
@ -732,11 +733,30 @@ impl App {
self.midi_gen,
iced::stream::channel(100, |mut output| async move {
loop {
let _ = output.send(Message::BleStatus("Looking for MIDI device...".into())).await;
match crate::ble::connect_and_stream(&mut output).await {
Ok(()) => eprintln!("BLE: session ended cleanly"),
Err(e) => eprintln!("BLE: session error: {e}"),
let (ble_tx, mut ble_rx) = mpsc::unbounded_channel::<BleEvent>();
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel::<Vec<u8>>();
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;
tokio::time::sleep(Duration::from_millis(500)).await;
}

View File

@ -1,29 +1,31 @@
use futures::SinkExt;
use midir::{MidiInput, MidiOutput, MidiInputConnection, MidiOutputConnection};
use std::sync::mpsc as std_mpsc;
use tokio::sync::mpsc;
use crate::app::Message;
use crate::protocol;
use crate::protocol::{self, EisMessage};
const DEVICE_NAME: &str = "EIS4";
pub async fn connect_and_stream(
output: &mut futures::channel::mpsc::Sender<Message>,
#[derive(Debug, Clone)]
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>> {
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 {
match find_midi_ports() {
Some(found) => break found,
None => {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
if let Some(found) = find_midi_ports() {
break found;
}
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
};
eprintln!("BLE: found ports, connecting...");
let _ = output.send(Message::BleStatus("Connecting MIDI...".into())).await;
let _ = tx.send(BleEvent::Status("Connecting MIDI...".into()));
let (sysex_tx, sysex_rx) = std_mpsc::channel::<Vec<u8>>();
@ -41,16 +43,12 @@ pub async fn connect_and_stream(
&out_port, "cue-out",
).map_err(|e| format!("MIDI output connect: {e}"))?;
eprintln!("BLE: connected");
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;
let _ = tx.send(BleEvent::Status("Connected".into()));
loop {
while let Ok(sysex) = sysex_rx.try_recv() {
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() {
Ok(pkt) => {
if let Err(e) = out_conn.send(&pkt) {
eprintln!("BLE: MIDI send error: {e}");
return Err(e.into());
eprintln!("MIDI send error: {e}");
}
}
Err(mpsc::error::TryRecvError::Disconnected) => {
eprintln!("BLE: cmd channel closed");
return Ok(());
}
Err(mpsc::error::TryRecvError::Disconnected) => return Ok(()),
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_out = MidiOutput::new("cue-out").ok()?;
let in_ports = midi_in.ports();
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| {
let in_port = midi_in.ports().into_iter().find(|p| {
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))
})?;

View File

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

View File

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

View File

@ -4,6 +4,8 @@
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "nvs.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
@ -21,6 +23,14 @@ static struct {
uint32_t dertia_reg;
} 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[] = {
[RTIA_200] = HSTIARTIA_200,
[RTIA_1K] = HSTIARTIA_1K,
@ -127,9 +137,13 @@ void eis_default_config(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;
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;
if (n > EIS_MAX_POINTS) n = EIS_MAX_POINTS;
if (n < 2) n = 2;
@ -164,7 +178,7 @@ void eis_init(const EISConfig *cfg)
ref.LpRefBufEn = bFALSE;
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_1, AFEINTSRC_DFTRDY, bTRUE);
@ -214,7 +228,7 @@ static void configure_freq(float freq_hz)
fp.DftSrc = DFTSRC_ADCRAW;
fp.ADCSinc3Osr = ADCSINC3OSR_2;
fp.ADCSinc2Osr = 0;
fp.DftNum = DFTNUM_16384;
fp.DftNum = DFTNUM_4096;
}
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;
}
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_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_INTCClrFlag(AFEINTSRC_DFTRDY);
AD5940_ADCMuxCfgS(mux_p, mux_n);
AD5940_ADCMuxCfgS(mux1_p, mux1_n);
AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR, bTRUE);
AD5940_Delay10us(25);
AD5940_ClrMCUIntFlag();
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
while (!AD5940_GetMCUIntFlag())
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 |
AFECTRL_WG | AFECTRL_ADCPWR, bFALSE);
AD5940_INTCClrFlag(AFEINTSRC_DFTRDY);
out->Real = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTREAL));
out->Image = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTIMAGE));
out->Image = -out->Image;
out2->Real = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTREAL));
out2->Image = sign_extend_18(AD5940_ReadAfeResult(AFERESULT_DFTIMAGE));
out2->Image = -out2->Image;
}
/* RTIA calibration: 2 DFTs through current RCAL switch config */
static fImpCar_Type measure_rtia(void)
static fImpCar_Type measure_rtia(iImpCar_Type *out_hstia)
{
iImpCar_Type v_rcal, v_raw;
dft_measure(ADCMUXP_P_NODE, ADCMUXN_N_NODE, &v_rcal);
dft_measure(ADCMUXP_HSTIA_P, ADCMUXN_HSTIA_N, &v_raw);
dft_measure_pair(
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.Image = -v_raw.Image;
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_SINC2NOTCH, bTRUE);
/* RCAL before */
fImpCar_Type rtia_before = measure_rtia();
/* RCAL before — capture raw HSTIA DFT for ratiometric diagnostic */
iImpCar_Type rcal_hstia;
fImpCar_Type rtia_before = measure_rtia(&rcal_hstia);
/* DUT forward */
sw.Dswitch = ctx.dut_sw_d;
@ -320,10 +354,11 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AD5940_SWMatrixCfgS(&sw);
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.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_sense_fwd = v_sense;
@ -336,7 +371,7 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AD5940_SWMatrixCfgS(&sw);
AD5940_Delay10us(50);
fImpCar_Type rtia_after = measure_rtia();
fImpCar_Type rtia_after = measure_rtia(NULL);
/* DUT reverse (DUT first, then RCAL) */
sw.Dswitch = ctx.dut_sw_d;
@ -346,10 +381,10 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AD5940_SWMatrixCfgS(&sw);
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.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_sense_rev = v_sense;
@ -362,7 +397,7 @@ int eis_measure_point(float freq_hz, EISPoint *out)
AD5940_SWMatrixCfgS(&sw);
AD5940_Delay10us(50);
fImpCar_Type rtia_rev = measure_rtia();
fImpCar_Type rtia_rev = measure_rtia(NULL);
/* power down, open switches */
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 };
num = AD5940_ComplexMulFloat(&fs_rev, &rtia_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_rev = AD5940_ComplexMag(&z_rev);
out->freq_hz = freq_hz;
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->rtia_mag_before = AD5940_ComplexMag(&rtia_before);
out->rtia_mag_after = AD5940_ComplexMag(&rtia_after);
out->rev_mag = mag_rev;
out->rev_phase = AD5940_ComplexPhase(&z_rev) * (float)(180.0 / M_PI);
out->pct_err = (mag_fwd > 0.0f)
? fabsf(mag_fwd - mag_rev) / mag_fwd * 100.0f : 0.0f;
out->rev_mag = AD5940_ComplexMag(&z_ratio);
out->rev_phase = AD5940_ComplexPhase(&z_ratio) * (float)(180.0 / M_PI);
out->pct_err = 0.0f;
return 0;
}
@ -427,23 +481,33 @@ int eis_sweep(EISPoint *out, uint32_t max_points, eis_point_cb_t cb)
sweep.SweepLog = bTRUE;
sweep.SweepIndex = 0;
printf("\n%10s %12s %10s %12s %12s %7s\n",
"Freq(Hz)", "|Z|(Ohm)", "Phase(deg)", "Re(Ohm)", "Im(Ohm)", "Err%");
printf("---------------------------------------------------------------------\n");
printf("\n%10s %12s %10s %12s %12s | %12s %10s %6s\n",
"Freq(Hz)", "|Z|dual", "Ph_dual", "Re_dual", "Im_dual",
"|Z|ratio", "Ph_ratio", "ms");
printf("--------------------------------------------------------------------------"
"-------------------------\n");
uint32_t t0 = xTaskGetTickCount();
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].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]);
for (uint32_t i = 1; i < n; i++) {
float freq;
AD5940_SweepNext(&sweep, &freq);
t0 = xTaskGetTickCount();
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].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]);
}
@ -453,3 +517,71 @@ int eis_sweep(EISPoint *out, uint32_t max_points, eis_point_cb_t cb)
AD5940_AFECtrlS(AFECTRL_ALL, bFALSE);
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);
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

View File

@ -52,6 +52,7 @@ void app_main(void)
if (adiid != AD5941_EXPECTED_ADIID) return;
eis_default_config(&cfg);
eis_load_open_cal();
temp_init();
esp_log_level_set("NimBLE", ESP_LOG_WARN);
@ -183,6 +184,22 @@ void app_main(void)
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;