Added basic UI for configuring the PLL

Switched frequencies to Hz as a base unit
Fixed formatting of plots
This commit is contained in:
Sebastian 2024-01-07 22:24:37 +01:00
parent 3ca1f112e7
commit be963830d4
7 changed files with 224 additions and 51 deletions

View File

@ -323,6 +323,7 @@ mod app {
let diff = freq as i64 - last_freq as i64; let diff = freq as i64 - last_freq as i64;
last_freq = freq; last_freq = freq;
if diff.abs() > 50_000 { if diff.abs() > 50_000 {
defmt::info!("Out of range, dropping sample."); defmt::info!("Out of range, dropping sample.");
continue; continue;
@ -433,7 +434,7 @@ mod app {
to_vec_cobs::<DeviceMessage, USB_BUFFER_SIZE>(&device_msg).unwrap(); to_vec_cobs::<DeviceMessage, USB_BUFFER_SIZE>(&device_msg).unwrap();
serial.write(bytes.as_slice()).unwrap(); serial.write(bytes.as_slice()).unwrap();
} }
HostMessage::SetPLLOutputs => { HostMessage::SetPLLOutputs(_) => {
defmt::error!("PLL output is not implemented yet") defmt::error!("PLL output is not implemented yet")
} }
}, },

View File

@ -10,5 +10,6 @@ cheapsdo-protocol = { path = "../protocol" }
crossbeam-channel = "0.5.11" crossbeam-channel = "0.5.11"
eframe = "0.24.1" eframe = "0.24.1"
egui_plot = "0.24.1" egui_plot = "0.24.1"
numfmt = "1.1.1"
postcard = {version = "1.0.8", features = ["use-std"]} postcard = {version = "1.0.8", features = ["use-std"]}
serialport = "4.3.0" serialport = "4.3.0"

View File

@ -1,12 +1,21 @@
pub fn frequency(freq: f64) -> String { use numfmt::{Formatter, Precision};
let rest = (freq * 1_000_000_000.0) as u64;
let milli_hz = rest % 1000;
let rest = rest / 1000;
let hz = rest % 1000;
let rest = rest / 1000;
let khz = rest % 1000;
let rest = rest / 1000;
let mhz = rest % 1000;
format!("{:02}.{:03} {:03} {:03} MHz", mhz, khz, hz, milli_hz) pub fn format_frequency(freq: f64) -> String {
let mut formatter = Formatter::new()
.separator(' ')
.unwrap()
.precision(Precision::Decimals(0));
formatter.fmt2(freq).to_owned()
}
pub fn format_frequency_mhz(freq: f64) -> String {
let mut formatter = Formatter::new()
.separator(' ')
.unwrap()
.suffix(" Hz")
.unwrap()
.precision(Precision::Decimals(3));
formatter.fmt2(freq).to_owned()
} }

View File

@ -1,5 +1,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui::{self};
use std::io; use std::io;
use std::io::Write; use std::io::Write;
@ -7,15 +9,12 @@ use std::time::Duration;
use crossbeam_channel::{select, unbounded, Receiver, Sender}; use crossbeam_channel::{select, unbounded, Receiver, Sender};
use eframe::egui::{self, RichText};
use cheapsdo_protocol::*; use cheapsdo_protocol::*;
use postcard::{from_bytes_cobs, to_stdvec_cobs}; use postcard::{from_bytes_cobs, to_stdvec_cobs};
mod formatters; mod formatters;
mod plots; mod plots;
mod widgets;
fn main() -> Result<(), eframe::Error> { fn main() -> Result<(), eframe::Error> {
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
@ -49,6 +48,13 @@ struct CheapsdoControl {
device_state: StatusMessage, device_state: StatusMessage,
average_points: Vec<[f64; 2]>, average_points: Vec<[f64; 2]>,
pwm_points: Vec<[f64; 2]>, pwm_points: Vec<[f64; 2]>,
ms1_frequency_id: egui::Id,
ms2_frequency_id: egui::Id,
output_frequency_id: [egui::Id; 3],
pll_settings: PLLSettings,
} }
impl Default for CheapsdoControl { impl Default for CheapsdoControl {
@ -56,12 +62,23 @@ impl Default for CheapsdoControl {
Self { Self {
serial_device: "".to_owned(), serial_device: "".to_owned(),
serial_connected: false, serial_connected: false,
data_rx: None, data_rx: None,
cmd_tx: None, cmd_tx: None,
device_state: StatusMessage::default(), device_state: StatusMessage::default(),
average_points: Vec::new(), average_points: Vec::new(),
pwm_points: Vec::new(), pwm_points: Vec::new(),
ms1_frequency_id: egui::Id::new("ms1_frequency_id"),
ms2_frequency_id: egui::Id::new("ms2_frequency_id"),
output_frequency_id: [
egui::Id::new("output_frequency1_id"),
egui::Id::new("output_frequency2_id"),
egui::Id::new("output_frequency3_id"),
],
pll_settings: PLLSettings::default(),
} }
} }
} }
@ -80,7 +97,7 @@ impl eframe::App for CheapsdoControl {
self.device_state = status_msg.clone(); self.device_state = status_msg.clone();
self.average_points.push([ self.average_points.push([
self.average_points.len() as f64, self.average_points.len() as f64,
status_msg.average_frequency as f64 / 1_000_000_000.0 status_msg.average_frequency as f64 / 1000.0
]); ]);
self.pwm_points.push([ self.pwm_points.push([
self.pwm_points.len() as f64, self.pwm_points.len() as f64,
@ -161,41 +178,60 @@ impl eframe::App for CheapsdoControl {
ui.vertical(|ui| { ui.vertical(|ui| {
ui.label(egui::RichText::new("Output Settings").size(20.0)); ui.label(egui::RichText::new("Output Settings").size(20.0));
ui.add_space(20.0); ui.add_space(20.0);
let mut my_string = String::new();
#[derive(PartialEq)]
enum Enum {
First,
Second,
}
let mut my_enum = Enum::First;
egui::Grid::new("output_pll_settings") egui::Grid::new("output_pll_settings")
.spacing([20.0, 5.0]) .spacing([20.0, 5.0])
.show(ui, |ui| { .show(ui, |ui| {
ui.label("CLK1 Multisynth"); ui.label("MS1 Frequency [Hz]");
ui.horizontal(|ui| { widgets::frequency_input(
ui.selectable_value(&mut my_enum, Enum::First, "MS1"); self.ms1_frequency_id,
ui.selectable_value(&mut my_enum, Enum::Second, "MS2"); &mut self.pll_settings.ms1_frequency,
}); &ctx,
ui,
);
ui.end_row(); ui.end_row();
ui.label("CLK1 Frequency [Hz]"); ui.label("MS2 Frequency [Hz]");
ui.add_sized([200.0, 20.0], egui::TextEdit::singleline(&mut my_string)); widgets::frequency_input(
ui.end_row(); self.ms2_frequency_id,
&mut self.pll_settings.ms2_frequency,
ui.label("CLK1 Enable"); &ctx,
ui.horizontal(|ui| { ui,
ui.add(egui::Button::new( );
RichText::new("On").color(egui::Color32::GREEN),
));
ui.add(egui::Button::new(
RichText::new("Off").color(egui::Color32::RED),
));
});
ui.end_row(); ui.end_row();
}); });
for i in 0..3 {
ui.separator();
egui::Grid::new(format!("output_clk{}_settings", i + 1))
.spacing([20.0, 5.0])
.show(ui, |ui| {
ui.label(format!("CLK{} Multisynth", i + 1));
widgets::multisynth_selector(
&mut self.pll_settings.outputs[i].source,
ui,
);
ui.end_row();
ui.label(format!("CLK{} Frequency [Hz]", i + 1));
widgets::frequency_input(
self.output_frequency_id[i],
&mut self.pll_settings.outputs[i].frequency,
&ctx,
ui,
);
ui.end_row();
ui.label(format!("CLK{} Enable", i + 1));
widgets::on_off_toggle(
&mut self.pll_settings.outputs[i].enable,
ui,
);
ui.end_row();
});
}
}); });
}); });
}); });

View File

@ -1,7 +1,7 @@
use eframe::egui; use eframe::egui;
use egui_plot::{Line, Plot, PlotPoints}; use egui_plot::{Line, Plot, PlotPoints};
use crate::{formatters}; use crate::formatters;
use crate::StatusMessage; use crate::StatusMessage;
@ -10,7 +10,7 @@ pub fn show_freuqencies(ui: &mut egui::Ui, state: &StatusMessage) {
ui.label( ui.label(
egui::RichText::new(format!( egui::RichText::new(format!(
"Measured: {}", "Measured: {}",
formatters::frequency(state.measured_frequency as f64/ 1_000_000_000.0) formatters::format_frequency_mhz(state.measured_frequency as f64 / 1000.0)
)) ))
.family(egui::FontFamily::Monospace) .family(egui::FontFamily::Monospace)
.size(20.0), .size(20.0),
@ -21,7 +21,7 @@ pub fn show_freuqencies(ui: &mut egui::Ui, state: &StatusMessage) {
ui.label( ui.label(
egui::RichText::new(format!( egui::RichText::new(format!(
"Average: {}", "Average: {}",
formatters::frequency(state.average_frequency as f64 / 1_000_000_000.0) formatters::format_frequency_mhz(state.average_frequency as f64 / 1000.0)
)) ))
.family(egui::FontFamily::Monospace) .family(egui::FontFamily::Monospace)
.size(20.0), .size(20.0),
@ -29,7 +29,7 @@ pub fn show_freuqencies(ui: &mut egui::Ui, state: &StatusMessage) {
}); });
} }
pub fn show_plots(ui: &mut egui::Ui, average_points: &Vec<[f64; 2]>, pwm_points: &Vec<[f64; 2]>) { pub fn show_plots(ui: &mut egui::Ui, average_points: &Vec<[f64; 2]>, pwm_points: &Vec<[f64; 2]>) {
let average_line = Line::new(PlotPoints::new(average_points.clone())); let average_line = Line::new(PlotPoints::new(average_points.clone()));
Plot::new("frequency_plot") Plot::new("frequency_plot")
.width(900.0) .width(900.0)
@ -39,8 +39,19 @@ pub fn show_plots(ui: &mut egui::Ui, average_points: &Vec<[f64; 2]>, pwm_points
.allow_drag(false) .allow_drag(false)
.allow_boxed_zoom(false) .allow_boxed_zoom(false)
.y_axis_width(12) .y_axis_width(12)
.y_axis_formatter(|val, _, _| formatters::frequency(val)) .y_axis_formatter(|val, _, _| formatters::format_frequency_mhz(val))
.label_formatter(|name, value| format!("{}: {}", name, formatters::frequency(value.y))) .label_formatter(|name, value| {
if !name.is_empty() {
format!("{}: {}", name, formatters::format_frequency_mhz(value.y))
} else {
format!(
"x = {}\ny= {}",
value.x,
formatters::format_frequency_mhz(value.y)
)
}
})
.link_cursor("plots", true, false)
.show(ui, |plot_ui| { .show(ui, |plot_ui| {
plot_ui.set_auto_bounds([true, true].into()); plot_ui.set_auto_bounds([true, true].into());
plot_ui.line(average_line); plot_ui.line(average_line);
@ -58,6 +69,7 @@ pub fn show_plots(ui: &mut egui::Ui, average_points: &Vec<[f64; 2]>, pwm_points
.allow_drag(false) .allow_drag(false)
.allow_boxed_zoom(false) .allow_boxed_zoom(false)
.y_axis_width(12) .y_axis_width(12)
.link_cursor("plots", true, false)
.show(ui, |plot_ui| { .show(ui, |plot_ui| {
plot_ui.set_auto_bounds([true, true].into()); plot_ui.set_auto_bounds([true, true].into());
plot_ui.line(pwm_line); plot_ui.line(pwm_line);

View File

@ -0,0 +1,70 @@
use cheapsdo_protocol::Multisynth;
use eframe::egui::{self, RichText};
use crate::formatters;
pub fn frequency_input(
id: egui::Id,
freq: &mut u32,
ctx: &egui::Context,
ui: &mut egui::Ui,
) -> egui::Response {
let has_focus = ctx.memory(|m| m.has_focus(id));
let mut tmp = if has_focus {
freq.to_string()
} else {
formatters::format_frequency(*freq as f64)
};
let resp = ui.add_sized(
[100.0, 20.0],
egui::TextEdit::singleline(&mut tmp)
.id(id)
.horizontal_align(egui::Align::Max),
);
if has_focus {
if let Ok(val) = tmp.parse::<u32>() {
*freq = val;
}
}
resp
}
pub fn multisynth_selector(
selected_source: &mut Multisynth,
ui: &mut egui::Ui,
) -> egui::InnerResponse<()> {
ui.horizontal(|ui| {
ui.selectable_value(selected_source, Multisynth::MS1, "MS1");
ui.selectable_value(selected_source, Multisynth::MS2, "MS2");
})
}
pub fn on_off_toggle(toggle: &mut bool, ui: &mut egui::Ui) -> egui::InnerResponse<()> {
let on_text = if *toggle {
RichText::new("On").color(egui::Color32::GREEN)
} else {
RichText::new("On")
};
let off_text = if *toggle {
RichText::new("Off")
} else {
RichText::new("Off").color(egui::Color32::RED)
};
ui.horizontal(|ui| {
if ui
.add_enabled(!*toggle, egui::Button::new(on_text))
.clicked()
{
*toggle = true;
}
if ui
.add_enabled(*toggle, egui::Button::new(off_text))
.clicked()
{
*toggle = false;
}
})
}

View File

@ -10,7 +10,7 @@ pub enum DeviceMessage {
#[derive(Serialize, Deserialize, Debug, PartialEq)] #[derive(Serialize, Deserialize, Debug, PartialEq)]
pub enum HostMessage { pub enum HostMessage {
RequestStatus, RequestStatus,
SetPLLOutputs, SetPLLOutputs(PLLSettings),
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
@ -29,3 +29,47 @@ impl Default for StatusMessage {
} }
} }
} }
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct PLLSettings {
pub ms1_frequency: u32,
pub ms2_frequency: u32,
pub outputs: [OutputSettings; 3],
}
impl Default for PLLSettings {
fn default() -> Self {
Self {
ms1_frequency: 800_000_000,
ms2_frequency: 800_000_000,
outputs: [
OutputSettings::default(),
OutputSettings::default(),
OutputSettings::default(),
],
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct OutputSettings {
pub frequency: u32,
pub enable: bool,
pub source: Multisynth,
}
impl Default for OutputSettings {
fn default() -> Self {
Self {
frequency: 10_000_000,
enable: false,
source: Multisynth::MS1,
}
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub enum Multisynth {
MS1,
MS2,
}