piecemeal
This commit is contained in:
parent
61287e5b8e
commit
10096738cf
|
|
@ -1,5 +1,5 @@
|
||||||
use futures::SinkExt;
|
use futures::SinkExt;
|
||||||
use iced::widget::{button, column, container, pick_list, row, scrollable, text, text_input};
|
use iced::widget::{button, canvas, column, container, pick_list, row, scrollable, text, text_input};
|
||||||
use iced::{Element, Length, Subscription, Task, Theme};
|
use iced::{Element, Length, Subscription, Task, Theme};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
@ -222,7 +222,15 @@ impl App {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = column![status, controls, scrollable(data_rows)]
|
let bode = canvas(crate::plot::BodePlot { points: &self.points })
|
||||||
|
.width(Length::FillPortion(3))
|
||||||
|
.height(300);
|
||||||
|
let nyquist = canvas(crate::plot::NyquistPlot { points: &self.points })
|
||||||
|
.width(Length::FillPortion(2))
|
||||||
|
.height(300);
|
||||||
|
let plots = row![bode, nyquist].spacing(10);
|
||||||
|
|
||||||
|
let content = column![status, controls, plots, scrollable(data_rows)]
|
||||||
.spacing(20)
|
.spacing(20)
|
||||||
.padding(20);
|
.padding(20);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
mod app;
|
mod app;
|
||||||
mod ble;
|
mod ble;
|
||||||
|
mod plot;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
|
|
||||||
fn main() -> iced::Result {
|
fn main() -> iced::Result {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,445 @@
|
||||||
|
use iced::widget::canvas::{self, Event, Frame, Geometry, Path, Stroke, Text};
|
||||||
|
use iced::{Color, Point, Rectangle, Renderer, Theme};
|
||||||
|
use iced::mouse;
|
||||||
|
|
||||||
|
use crate::app::Message;
|
||||||
|
use crate::protocol::EisPoint;
|
||||||
|
|
||||||
|
const MARGIN_L: f32 = 55.0;
|
||||||
|
const MARGIN_R: f32 = 15.0;
|
||||||
|
const MARGIN_T: f32 = 15.0;
|
||||||
|
const MARGIN_B: f32 = 25.0;
|
||||||
|
|
||||||
|
const COL_MAG: Color = Color { r: 0.3, g: 0.85, b: 1.0, a: 1.0 };
|
||||||
|
const COL_PH: Color = Color { r: 1.0, g: 0.55, b: 0.2, a: 1.0 };
|
||||||
|
const COL_NYQ: Color = Color { r: 0.4, g: 1.0, b: 0.4, a: 1.0 };
|
||||||
|
const COL_GRID: Color = Color { r: 0.25, g: 0.25, b: 0.28, a: 1.0 };
|
||||||
|
const COL_AXIS: Color = Color { r: 0.6, g: 0.6, b: 0.6, a: 1.0 };
|
||||||
|
const COL_DIM: Color = Color { r: 0.4, g: 0.4, b: 0.4, a: 1.0 };
|
||||||
|
|
||||||
|
const ZOOM_FACTOR: f32 = 1.15;
|
||||||
|
|
||||||
|
/* ---- View range ---- */
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Vr {
|
||||||
|
lo: f32,
|
||||||
|
hi: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vr {
|
||||||
|
fn new(lo: f32, hi: f32) -> Self { Self { lo, hi } }
|
||||||
|
fn span(&self) -> f32 { self.hi - self.lo }
|
||||||
|
|
||||||
|
fn zoom_at(&mut self, factor: f32, anchor_frac: f32) {
|
||||||
|
let anchor = self.lo + anchor_frac * self.span();
|
||||||
|
let new_span = self.span() / factor;
|
||||||
|
self.lo = anchor - anchor_frac * new_span;
|
||||||
|
self.hi = anchor + (1.0 - anchor_frac) * new_span;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pan_frac(&mut self, frac: f32) {
|
||||||
|
let d = frac * self.span();
|
||||||
|
self.lo -= d;
|
||||||
|
self.hi -= d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn screen_frac(pos: f32, lo: f32, hi: f32) -> f32 {
|
||||||
|
if (hi - lo).abs() < 1e-6 { 0.5 } else { (pos - lo) / (hi - lo) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Drawing helpers ---- */
|
||||||
|
|
||||||
|
fn lerp(v: f32, lo: f32, hi: f32, out_lo: f32, out_hi: f32) -> f32 {
|
||||||
|
if (hi - lo).abs() < 1e-10 { return (out_lo + out_hi) / 2.0; }
|
||||||
|
out_lo + (v - lo) / (hi - lo) * (out_hi - out_lo)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nice_step(range: f32, target_ticks: usize) -> f32 {
|
||||||
|
if range.abs() < 1e-10 { return 1.0; }
|
||||||
|
let rough = range / target_ticks as f32;
|
||||||
|
let mag = 10f32.powf(rough.abs().log10().floor());
|
||||||
|
let norm = rough.abs() / mag;
|
||||||
|
let s = if norm < 1.5 { 1.0 } else if norm < 3.5 { 2.0 } else if norm < 7.5 { 5.0 } else { 10.0 };
|
||||||
|
s * mag
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dl(frame: &mut Frame, a: Point, b: Point, color: Color, width: f32) {
|
||||||
|
frame.stroke(&Path::line(a, b), Stroke::default().with_color(color).with_width(width));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dt(frame: &mut Frame, pos: Point, txt: &str, color: Color, size: f32) {
|
||||||
|
frame.fill_text(Text {
|
||||||
|
content: txt.to_string(), position: pos, color, size: size.into(),
|
||||||
|
..Text::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_polyline(frame: &mut Frame, pts: &[Point], color: Color, width: f32) {
|
||||||
|
if pts.len() < 2 { return; }
|
||||||
|
let path = Path::new(|b| {
|
||||||
|
b.move_to(pts[0]);
|
||||||
|
for p in &pts[1..] { b.line_to(*p); }
|
||||||
|
});
|
||||||
|
frame.stroke(&path, Stroke::default().with_color(color).with_width(width));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_dots(frame: &mut Frame, pts: &[Point], color: Color, r: f32) {
|
||||||
|
for p in pts { frame.fill(&Path::circle(*p, r), color); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Bode state: zoom/pan on frequency axis, Y auto-scales ---- */
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BodeState {
|
||||||
|
freq: Option<Vr>,
|
||||||
|
drag: Option<(f32, Vr)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BodePlot<'a> {
|
||||||
|
pub points: &'a [EisPoint],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodePlot<'_> {
|
||||||
|
fn auto_freq(&self) -> Option<Vr> {
|
||||||
|
if self.points.is_empty() { return None; }
|
||||||
|
let lo = self.points.iter().map(|p| p.freq_hz.log10()).fold(f32::INFINITY, f32::min);
|
||||||
|
let hi = self.points.iter().map(|p| p.freq_hz.log10()).fold(f32::NEG_INFINITY, f32::max);
|
||||||
|
let pad = (hi - lo).max(0.1) * 0.05;
|
||||||
|
Some(Vr::new(lo - pad, hi + pad))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> canvas::Program<Message> for BodePlot<'a> {
|
||||||
|
type State = BodeState;
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&self, state: &mut BodeState, event: Event,
|
||||||
|
bounds: Rectangle, cursor: mouse::Cursor,
|
||||||
|
) -> (canvas::event::Status, Option<Message>) {
|
||||||
|
let Some(pos) = cursor.position_in(bounds) else {
|
||||||
|
return (canvas::event::Status::Ignored, None);
|
||||||
|
};
|
||||||
|
let xl = MARGIN_L;
|
||||||
|
let xr = bounds.width - MARGIN_R;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
|
||||||
|
let dy = match delta {
|
||||||
|
mouse::ScrollDelta::Lines { y, .. } => y,
|
||||||
|
mouse::ScrollDelta::Pixels { y, .. } => y / 40.0,
|
||||||
|
};
|
||||||
|
let factor = ZOOM_FACTOR.powf(dy);
|
||||||
|
let frac = screen_frac(pos.x, xl, xr);
|
||||||
|
let mut vr = state.freq.unwrap_or_else(|| self.auto_freq().unwrap_or(Vr::new(3.0, 5.3)));
|
||||||
|
vr.zoom_at(factor, frac);
|
||||||
|
state.freq = Some(vr);
|
||||||
|
(canvas::event::Status::Captured, None)
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
|
let vr = state.freq.unwrap_or_else(|| self.auto_freq().unwrap_or(Vr::new(3.0, 5.3)));
|
||||||
|
state.drag = Some((pos.x, vr));
|
||||||
|
(canvas::event::Status::Captured, None)
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
||||||
|
if let Some((start_x, start_vr)) = state.drag {
|
||||||
|
let dx_frac = (pos.x - start_x) / (xr - xl);
|
||||||
|
let mut vr = start_vr;
|
||||||
|
vr.pan_frac(dx_frac);
|
||||||
|
state.freq = Some(vr);
|
||||||
|
return (canvas::event::Status::Captured, None);
|
||||||
|
}
|
||||||
|
(canvas::event::Status::Ignored, None)
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
||||||
|
state.drag = None;
|
||||||
|
(canvas::event::Status::Captured, None)
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
|
||||||
|
state.freq = None;
|
||||||
|
(canvas::event::Status::Captured, None)
|
||||||
|
}
|
||||||
|
_ => (canvas::event::Status::Ignored, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self, state: &BodeState, renderer: &Renderer, _theme: &Theme,
|
||||||
|
bounds: Rectangle, cursor: mouse::Cursor,
|
||||||
|
) -> Vec<Geometry> {
|
||||||
|
let mut frame = Frame::new(renderer, bounds.size());
|
||||||
|
let (w, h) = (bounds.width, bounds.height);
|
||||||
|
|
||||||
|
if self.points.is_empty() {
|
||||||
|
dt(&mut frame, Point::new(w / 2.0 - 25.0, h / 2.0), "No data", COL_DIM, 13.0);
|
||||||
|
return vec![frame.into_geometry()];
|
||||||
|
}
|
||||||
|
|
||||||
|
let split = (h * 0.55).floor();
|
||||||
|
let xl = MARGIN_L;
|
||||||
|
let xr = w - MARGIN_R;
|
||||||
|
|
||||||
|
let fv = state.freq.unwrap_or_else(|| self.auto_freq().unwrap());
|
||||||
|
|
||||||
|
// filter visible points for Y auto-scale
|
||||||
|
let vis: Vec<&EisPoint> = self.points.iter()
|
||||||
|
.filter(|p| { let lf = p.freq_hz.log10(); lf >= fv.lo && lf <= fv.hi })
|
||||||
|
.collect();
|
||||||
|
let all = if vis.is_empty() { self.points.iter().collect::<Vec<_>>() } else { vis };
|
||||||
|
|
||||||
|
let (m_min, m_max) = all.iter().fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), p| {
|
||||||
|
(lo.min(p.mag_ohms), hi.max(p.mag_ohms))
|
||||||
|
});
|
||||||
|
let m_pad = (m_max - m_min).max(1.0) * 0.12;
|
||||||
|
let (m_lo, m_hi) = (m_min - m_pad, m_max + m_pad);
|
||||||
|
|
||||||
|
let (p_min, p_max) = all.iter().fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), p| {
|
||||||
|
(lo.min(p.phase_deg), hi.max(p.phase_deg))
|
||||||
|
});
|
||||||
|
let p_pad = (p_max - p_min).max(0.1) * 0.25;
|
||||||
|
let (p_lo, p_hi) = (p_min - p_pad, p_max + p_pad);
|
||||||
|
|
||||||
|
// freq grid
|
||||||
|
let d0 = fv.lo.floor() as i32;
|
||||||
|
let d1 = fv.hi.ceil() as i32;
|
||||||
|
for d in d0..=d1 {
|
||||||
|
let x = lerp(d as f32, fv.lo, fv.hi, xl, xr);
|
||||||
|
if x < xl || x > xr { continue; }
|
||||||
|
dl(&mut frame, Point::new(x, MARGIN_T), Point::new(x, h - MARGIN_B), COL_GRID, 1.0);
|
||||||
|
let hz = 10f32.powi(d);
|
||||||
|
let label = if hz >= 1000.0 { format!("{}k", hz as u32 / 1000) } else { format!("{}", hz as u32) };
|
||||||
|
dt(&mut frame, Point::new(x - 8.0, split - 2.0), &label, COL_DIM, 9.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
dl(&mut frame, Point::new(xl, split), Point::new(xr, split), COL_AXIS, 1.0);
|
||||||
|
|
||||||
|
// magnitude
|
||||||
|
let mag_top = MARGIN_T;
|
||||||
|
let mag_bot = split - 14.0;
|
||||||
|
let m_step = nice_step(m_hi - m_lo, 4);
|
||||||
|
if m_step > 0.0 {
|
||||||
|
let mut mg = (m_lo / m_step).ceil() * m_step;
|
||||||
|
while mg <= m_hi {
|
||||||
|
let y = lerp(mg, m_hi, m_lo, mag_top, mag_bot);
|
||||||
|
dl(&mut frame, Point::new(xl, y), Point::new(xr, y), COL_GRID, 0.5);
|
||||||
|
dt(&mut frame, Point::new(2.0, y - 5.0), &format!("{:.0}", mg), COL_MAG, 9.0);
|
||||||
|
mg += m_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dt(&mut frame, Point::new(2.0, mag_top - 2.0), "|Z|", COL_MAG, 10.0);
|
||||||
|
|
||||||
|
let mag_pts: Vec<Point> = self.points.iter().map(|p| Point::new(
|
||||||
|
lerp(p.freq_hz.log10(), fv.lo, fv.hi, xl, xr),
|
||||||
|
lerp(p.mag_ohms, m_hi, m_lo, mag_top, mag_bot),
|
||||||
|
)).collect();
|
||||||
|
draw_polyline(&mut frame, &mag_pts, COL_MAG, 2.0);
|
||||||
|
draw_dots(&mut frame, &mag_pts, COL_MAG, 2.5);
|
||||||
|
|
||||||
|
// phase
|
||||||
|
let ph_top = split + 12.0;
|
||||||
|
let ph_bot = h - MARGIN_B;
|
||||||
|
let p_step = nice_step(p_hi - p_lo, 3);
|
||||||
|
if p_step > 0.0 {
|
||||||
|
let mut pg = (p_lo / p_step).ceil() * p_step;
|
||||||
|
while pg <= p_hi {
|
||||||
|
let y = lerp(pg, p_hi, p_lo, ph_top, ph_bot);
|
||||||
|
dl(&mut frame, Point::new(xl, y), Point::new(xr, y), COL_GRID, 0.5);
|
||||||
|
dt(&mut frame, Point::new(2.0, y - 5.0), &format!("{:.1}", pg), COL_PH, 9.0);
|
||||||
|
pg += p_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dt(&mut frame, Point::new(2.0, ph_top - 2.0), "Phase", COL_PH, 10.0);
|
||||||
|
|
||||||
|
let ph_pts: Vec<Point> = self.points.iter().map(|p| Point::new(
|
||||||
|
lerp(p.freq_hz.log10(), fv.lo, fv.hi, xl, xr),
|
||||||
|
lerp(p.phase_deg, p_hi, p_lo, ph_top, ph_bot),
|
||||||
|
)).collect();
|
||||||
|
draw_polyline(&mut frame, &ph_pts, COL_PH, 2.0);
|
||||||
|
draw_dots(&mut frame, &ph_pts, COL_PH, 2.5);
|
||||||
|
|
||||||
|
// crosshair on hover
|
||||||
|
if let Some(pos) = cursor.position_in(bounds) {
|
||||||
|
if pos.x >= xl && pos.x <= xr && pos.y >= MARGIN_T && pos.y <= h - MARGIN_B {
|
||||||
|
let lf = lerp(pos.x, xl, xr, fv.lo, fv.hi);
|
||||||
|
let hz = 10f32.powf(lf);
|
||||||
|
let label = if hz >= 1000.0 { format!("{:.1}kHz", hz / 1000.0) } else { format!("{:.0}Hz", hz) };
|
||||||
|
dl(&mut frame, Point::new(pos.x, MARGIN_T), Point::new(pos.x, h - MARGIN_B),
|
||||||
|
Color { a: 0.3, ..COL_AXIS }, 1.0);
|
||||||
|
dt(&mut frame, Point::new(pos.x + 4.0, MARGIN_T + 2.0), &label, COL_AXIS, 10.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![frame.into_geometry()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Nyquist state: zoom/pan on both axes ---- */
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NyquistState {
|
||||||
|
view: Option<(Vr, Vr)>,
|
||||||
|
drag: Option<(Point, Vr, Vr)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NyquistPlot<'a> {
|
||||||
|
pub points: &'a [EisPoint],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NyquistPlot<'_> {
|
||||||
|
fn auto_view(&self) -> Option<(Vr, Vr)> {
|
||||||
|
if self.points.is_empty() { return None; }
|
||||||
|
let (re_min, re_max) = self.points.iter().fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), p| {
|
||||||
|
(lo.min(p.z_real), hi.max(p.z_real))
|
||||||
|
});
|
||||||
|
let (ni_min, ni_max) = self.points.iter().fold((f32::INFINITY, f32::NEG_INFINITY), |(lo, hi), p| {
|
||||||
|
(lo.min(-p.z_imag), hi.max(-p.z_imag))
|
||||||
|
});
|
||||||
|
let re_span = (re_max - re_min).max(1.0);
|
||||||
|
let ni_span = (ni_max - ni_min).max(1.0);
|
||||||
|
let span = re_span.max(ni_span) * 1.3;
|
||||||
|
let re_c = (re_min + re_max) / 2.0;
|
||||||
|
let ni_c = (ni_min + ni_max) / 2.0;
|
||||||
|
Some((Vr::new(re_c - span / 2.0, re_c + span / 2.0),
|
||||||
|
Vr::new(ni_c - span / 2.0, ni_c + span / 2.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> canvas::Program<Message> for NyquistPlot<'a> {
|
||||||
|
type State = NyquistState;
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
&self, state: &mut NyquistState, event: Event,
|
||||||
|
bounds: Rectangle, cursor: mouse::Cursor,
|
||||||
|
) -> (canvas::event::Status, Option<Message>) {
|
||||||
|
let Some(pos) = cursor.position_in(bounds) else {
|
||||||
|
return (canvas::event::Status::Ignored, None);
|
||||||
|
};
|
||||||
|
let xl = MARGIN_L;
|
||||||
|
let xr = bounds.width - MARGIN_R;
|
||||||
|
let yt = MARGIN_T;
|
||||||
|
let yb = bounds.height - MARGIN_B;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::Mouse(mouse::Event::WheelScrolled { delta }) => {
|
||||||
|
let dy = match delta {
|
||||||
|
mouse::ScrollDelta::Lines { y, .. } => y,
|
||||||
|
mouse::ScrollDelta::Pixels { y, .. } => y / 40.0,
|
||||||
|
};
|
||||||
|
let factor = ZOOM_FACTOR.powf(dy);
|
||||||
|
let fx = screen_frac(pos.x, xl, xr);
|
||||||
|
let fy = screen_frac(pos.y, yt, yb);
|
||||||
|
let (mut xv, mut yv) = state.view.unwrap_or_else(|| self.auto_view().unwrap_or(
|
||||||
|
(Vr::new(0.0, 1.0), Vr::new(0.0, 1.0))
|
||||||
|
));
|
||||||
|
xv.zoom_at(factor, fx);
|
||||||
|
yv.zoom_at(factor, 1.0 - fy);
|
||||||
|
state.view = Some((xv, yv));
|
||||||
|
(canvas::event::Status::Captured, None)
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||||
|
let (xv, yv) = state.view.unwrap_or_else(|| self.auto_view().unwrap_or(
|
||||||
|
(Vr::new(0.0, 1.0), Vr::new(0.0, 1.0))
|
||||||
|
));
|
||||||
|
state.drag = Some((pos, xv, yv));
|
||||||
|
(canvas::event::Status::Captured, None)
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
|
||||||
|
if let Some((start, sx, sy)) = state.drag {
|
||||||
|
let dx = (pos.x - start.x) / (xr - xl);
|
||||||
|
let dy = (pos.y - start.y) / (yb - yt);
|
||||||
|
let mut xv = sx;
|
||||||
|
let mut yv = sy;
|
||||||
|
xv.pan_frac(dx);
|
||||||
|
yv.pan_frac(-dy);
|
||||||
|
state.view = Some((xv, yv));
|
||||||
|
return (canvas::event::Status::Captured, None);
|
||||||
|
}
|
||||||
|
(canvas::event::Status::Ignored, None)
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
||||||
|
state.drag = None;
|
||||||
|
(canvas::event::Status::Captured, None)
|
||||||
|
}
|
||||||
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
|
||||||
|
state.view = None;
|
||||||
|
(canvas::event::Status::Captured, None)
|
||||||
|
}
|
||||||
|
_ => (canvas::event::Status::Ignored, None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(
|
||||||
|
&self, state: &NyquistState, renderer: &Renderer, _theme: &Theme,
|
||||||
|
bounds: Rectangle, cursor: mouse::Cursor,
|
||||||
|
) -> Vec<Geometry> {
|
||||||
|
let mut frame = Frame::new(renderer, bounds.size());
|
||||||
|
let (w, h) = (bounds.width, bounds.height);
|
||||||
|
|
||||||
|
if self.points.is_empty() {
|
||||||
|
dt(&mut frame, Point::new(w / 2.0 - 25.0, h / 2.0), "No data", COL_DIM, 13.0);
|
||||||
|
return vec![frame.into_geometry()];
|
||||||
|
}
|
||||||
|
|
||||||
|
let xl = MARGIN_L;
|
||||||
|
let xr = w - MARGIN_R;
|
||||||
|
let yt = MARGIN_T;
|
||||||
|
let yb = h - MARGIN_B;
|
||||||
|
|
||||||
|
let (xv, yv) = state.view.unwrap_or_else(|| self.auto_view().unwrap());
|
||||||
|
|
||||||
|
// grid
|
||||||
|
let x_step = nice_step(xv.span(), 4);
|
||||||
|
if x_step > 0.0 {
|
||||||
|
let mut g = (xv.lo / x_step).ceil() * x_step;
|
||||||
|
while g <= xv.hi {
|
||||||
|
let x = lerp(g, xv.lo, xv.hi, xl, xr);
|
||||||
|
dl(&mut frame, Point::new(x, yt), Point::new(x, yb), COL_GRID, 0.5);
|
||||||
|
dt(&mut frame, Point::new(x - 10.0, yb + 3.0), &format!("{:.0}", g), COL_DIM, 9.0);
|
||||||
|
g += x_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let y_step = nice_step(yv.span(), 4);
|
||||||
|
if y_step > 0.0 {
|
||||||
|
let mut g = (yv.lo / y_step).ceil() * y_step;
|
||||||
|
while g <= yv.hi {
|
||||||
|
let y = lerp(g, yv.hi, yv.lo, yt, yb);
|
||||||
|
dl(&mut frame, Point::new(xl, y), Point::new(xr, y), COL_GRID, 0.5);
|
||||||
|
dt(&mut frame, Point::new(2.0, y - 5.0), &format!("{:.0}", g), COL_DIM, 9.0);
|
||||||
|
g += y_step;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero line
|
||||||
|
let zy = lerp(0.0, yv.hi, yv.lo, yt, yb);
|
||||||
|
if zy > yt && zy < yb {
|
||||||
|
dl(&mut frame, Point::new(xl, zy), Point::new(xr, zy), COL_AXIS, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
dt(&mut frame, Point::new(2.0, yt - 2.0), "-Z''", COL_NYQ, 10.0);
|
||||||
|
dt(&mut frame, Point::new((xl + xr) / 2.0 - 12.0, yb + 3.0), "Z'", COL_NYQ, 10.0);
|
||||||
|
|
||||||
|
let pts: Vec<Point> = self.points.iter().map(|p| Point::new(
|
||||||
|
lerp(p.z_real, xv.lo, xv.hi, xl, xr),
|
||||||
|
lerp(-p.z_imag, yv.hi, yv.lo, yt, yb),
|
||||||
|
)).collect();
|
||||||
|
draw_polyline(&mut frame, &pts, COL_NYQ, 2.0);
|
||||||
|
draw_dots(&mut frame, &pts, COL_NYQ, 3.0);
|
||||||
|
|
||||||
|
// crosshair with values on hover
|
||||||
|
if let Some(pos) = cursor.position_in(bounds) {
|
||||||
|
if pos.x >= xl && pos.x <= xr && pos.y >= yt && pos.y <= yb {
|
||||||
|
let re = lerp(pos.x, xl, xr, xv.lo, xv.hi);
|
||||||
|
let nim = lerp(pos.y, yt, yb, yv.hi, yv.lo);
|
||||||
|
dl(&mut frame, Point::new(pos.x, yt), Point::new(pos.x, yb),
|
||||||
|
Color { a: 0.3, ..COL_AXIS }, 1.0);
|
||||||
|
dl(&mut frame, Point::new(xl, pos.y), Point::new(xr, pos.y),
|
||||||
|
Color { a: 0.3, ..COL_AXIS }, 1.0);
|
||||||
|
dt(&mut frame, Point::new(pos.x + 4.0, pos.y - 14.0),
|
||||||
|
&format!("{:.1}, {:.1}", re, nim), COL_AXIS, 10.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![frame.into_geometry()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -478,14 +478,17 @@ static int send_sysex(const uint8_t *sysex, uint16_t len)
|
||||||
if (conn_hdl == BLE_HS_CONN_HANDLE_NONE || !midi_notify_en)
|
if (conn_hdl == BLE_HS_CONN_HANDLE_NONE || !midi_notify_en)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
uint16_t pkt_len = 2 + len;
|
/* BLE MIDI SysEx: [header, ts, F0, payload..., ts, F7] */
|
||||||
|
uint16_t pkt_len = len + 3;
|
||||||
uint8_t pkt[80];
|
uint8_t pkt[80];
|
||||||
if (pkt_len > sizeof(pkt))
|
if (pkt_len > sizeof(pkt))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
pkt[0] = 0x80;
|
pkt[0] = 0x80;
|
||||||
pkt[1] = 0x80;
|
pkt[1] = 0x80;
|
||||||
memcpy(&pkt[2], sysex, len);
|
memcpy(&pkt[2], sysex, len - 1);
|
||||||
|
pkt[len + 1] = 0x80;
|
||||||
|
pkt[len + 2] = 0xF7;
|
||||||
|
|
||||||
struct os_mbuf *om = ble_hs_mbuf_from_flat(pkt, pkt_len);
|
struct os_mbuf *om = ble_hs_mbuf_from_flat(pkt, pkt_len);
|
||||||
if (!om) return -1;
|
if (!om) return -1;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
|
||||||
#ifndef M_PI
|
#ifndef M_PI
|
||||||
#define M_PI 3.14159265358979323846
|
#define M_PI 3.14159265358979323846
|
||||||
|
|
@ -235,12 +237,11 @@ static void dft_measure(uint32_t mux_p, uint32_t mux_n, iImpCar_Type *out)
|
||||||
AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR, bTRUE);
|
AD5940_AFECtrlS(AFECTRL_WG | AFECTRL_ADCPWR, bTRUE);
|
||||||
AD5940_Delay10us(25);
|
AD5940_Delay10us(25);
|
||||||
|
|
||||||
|
AD5940_ClrMCUIntFlag();
|
||||||
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
|
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT, bTRUE);
|
||||||
|
|
||||||
uint32_t timeout = 10000000;
|
while (!AD5940_GetMCUIntFlag())
|
||||||
while (AD5940_INTCTestFlag(AFEINTC_1, AFEINTSRC_DFTRDY) == bFALSE) {
|
vTaskDelay(1);
|
||||||
if (--timeout == 0) break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT |
|
AD5940_AFECtrlS(AFECTRL_ADCCNV | AFECTRL_DFT |
|
||||||
AFECTRL_WG | AFECTRL_ADCPWR, bFALSE);
|
AFECTRL_WG | AFECTRL_ADCPWR, bFALSE);
|
||||||
|
|
|
||||||
|
|
@ -1754,7 +1754,7 @@ CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0
|
||||||
#
|
#
|
||||||
# CONFIG_FREERTOS_SMP is not set
|
# CONFIG_FREERTOS_SMP is not set
|
||||||
# CONFIG_FREERTOS_UNICORE is not set
|
# CONFIG_FREERTOS_UNICORE is not set
|
||||||
CONFIG_FREERTOS_HZ=100
|
CONFIG_FREERTOS_HZ=1000
|
||||||
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set
|
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set
|
||||||
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set
|
# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set
|
||||||
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
|
CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue