recovered working tree: paired DFT, ratiometric Z, open-cal, BLE event refactor
This commit is contained in:
parent
5268d55b6f
commit
5ae607eec4
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
202
main/eis.c
202
main/eis.c
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
17
main/eis4.c
17
main/eis4.c
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue