Compare commits
7 Commits
main
...
pll-output
Author | SHA1 | Date |
---|---|---|
Sebastian | ec2f3608c4 | |
Sebastian | 6a29ab91da | |
Sebastian | e059e712a8 | |
Sebastian | c390413ca7 | |
Sebastian | 68f0fb6e93 | |
Sebastian | be963830d4 | |
Sebastian | 3ca1f112e7 |
|
@ -1,5 +1,4 @@
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
.#*
|
.#*
|
||||||
.gdb_history
|
.gdb_history
|
||||||
Cargo.lock
|
|
||||||
target/
|
target/
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -41,7 +41,7 @@ mod app {
|
||||||
use heapless::Vec;
|
use heapless::Vec;
|
||||||
use postcard::{from_bytes_cobs, to_vec_cobs};
|
use postcard::{from_bytes_cobs, to_vec_cobs};
|
||||||
|
|
||||||
use cheapsdo_protocol::{DeviceMessage, HostMessage, StatusMessage};
|
use cheapsdo_protocol::{DeviceMessage, HostMessage, PLLSettings, StatusMessage};
|
||||||
|
|
||||||
use crate::nvstate::{self, NVState};
|
use crate::nvstate::{self, NVState};
|
||||||
use crate::si5153;
|
use crate::si5153;
|
||||||
|
@ -54,8 +54,6 @@ mod app {
|
||||||
tim2: TIM2,
|
tim2: TIM2,
|
||||||
tim3: TIM3,
|
tim3: TIM3,
|
||||||
pwm: PwmHz<TIM4, Tim4NoRemap, timer::Ch<0>, gpio::Pin<'B', 6, Alternate>>,
|
pwm: PwmHz<TIM4, Tim4NoRemap, timer::Ch<0>, gpio::Pin<'B', 6, Alternate>>,
|
||||||
nvstate: NVState,
|
|
||||||
flash: flash::Parts,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[shared]
|
#[shared]
|
||||||
|
@ -64,6 +62,8 @@ mod app {
|
||||||
serial: usbd_serial::SerialPort<'static, UsbBusType>,
|
serial: usbd_serial::SerialPort<'static, UsbBusType>,
|
||||||
device_status: StatusMessage,
|
device_status: StatusMessage,
|
||||||
buffer: Vec<u8, USB_BUFFER_SIZE>,
|
buffer: Vec<u8, USB_BUFFER_SIZE>,
|
||||||
|
nvstate: NVState,
|
||||||
|
flash: flash::Parts,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TARGET_FREQ: u64 = 10_000_000_000; // in millihertz
|
const TARGET_FREQ: u64 = 10_000_000_000; // in millihertz
|
||||||
|
@ -246,21 +246,21 @@ mod app {
|
||||||
usb_dev,
|
usb_dev,
|
||||||
device_status: StatusMessage::default(),
|
device_status: StatusMessage::default(),
|
||||||
buffer: Vec::new(),
|
buffer: Vec::new(),
|
||||||
|
nvstate,
|
||||||
|
flash,
|
||||||
},
|
},
|
||||||
Local {
|
Local {
|
||||||
board_led,
|
board_led,
|
||||||
tim2,
|
tim2,
|
||||||
tim3,
|
tim3,
|
||||||
pwm,
|
pwm,
|
||||||
nvstate,
|
|
||||||
flash,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const WINDOW_LEN: usize = 100;
|
const WINDOW_LEN: usize = 100;
|
||||||
|
|
||||||
#[task(local=[tim2, tim3, pwm, board_led, nvstate, flash], shared=[device_status])]
|
#[task(local=[tim2, tim3, pwm, board_led], shared=[device_status, nvstate, flash])]
|
||||||
async fn update_pwm(mut cx: update_pwm::Context) {
|
async fn update_pwm(mut cx: update_pwm::Context) {
|
||||||
defmt::info!("Update Task started");
|
defmt::info!("Update Task started");
|
||||||
|
|
||||||
|
@ -268,11 +268,14 @@ mod app {
|
||||||
let tim3 = cx.local.tim3;
|
let tim3 = cx.local.tim3;
|
||||||
let pwm = cx.local.pwm;
|
let pwm = cx.local.pwm;
|
||||||
let board_led = cx.local.board_led;
|
let board_led = cx.local.board_led;
|
||||||
let mut nvstate = cx.local.nvstate;
|
|
||||||
let mut flash = cx.local.flash;
|
|
||||||
|
|
||||||
let max_pwm = pwm.get_max_duty() as i32;
|
let max_pwm = pwm.get_max_duty() as i32;
|
||||||
let mut cur_pwm = nvstate.pwm as i32;
|
|
||||||
|
let mut cur_pwm = 0i32;
|
||||||
|
cx.shared.nvstate.lock(|nvstate| {
|
||||||
|
cur_pwm = nvstate.pwm as i32;
|
||||||
|
});
|
||||||
|
|
||||||
cur_pwm = if cur_pwm < 0 { 0 } else { cur_pwm };
|
cur_pwm = if cur_pwm < 0 { 0 } else { cur_pwm };
|
||||||
cur_pwm = if cur_pwm > max_pwm { max_pwm } else { cur_pwm };
|
cur_pwm = if cur_pwm > max_pwm { max_pwm } else { cur_pwm };
|
||||||
|
|
||||||
|
@ -323,6 +326,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;
|
||||||
|
@ -361,39 +365,59 @@ mod app {
|
||||||
pwm.set_duty(Channel::C1, cur_pwm as u16);
|
pwm.set_duty(Channel::C1, cur_pwm as u16);
|
||||||
defmt::info!("pwm:\t{}", cur_pwm);
|
defmt::info!("pwm:\t{}", cur_pwm);
|
||||||
|
|
||||||
nvstate.pwm = cur_pwm as u16;
|
{
|
||||||
nvstate.save(&mut flash);
|
(&mut cx.shared.nvstate, &mut cx.shared.flash).lock(|nvstate, flash| {
|
||||||
|
nvstate.pwm = cur_pwm as u16;
|
||||||
|
nvstate.save(flash);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Systick::delay(500.millis()).await;
|
Systick::delay(500.millis()).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USB_HP_CAN_TX, shared = [usb_dev, serial, buffer, device_status])]
|
#[task(binds = USB_HP_CAN_TX, shared = [usb_dev, serial, buffer, device_status, nvstate, flash])]
|
||||||
fn usb_tx(cx: usb_tx::Context) {
|
fn usb_tx(cx: usb_tx::Context) {
|
||||||
let mut usb_dev = cx.shared.usb_dev;
|
let mut usb_dev = cx.shared.usb_dev;
|
||||||
let mut serial = cx.shared.serial;
|
let mut serial = cx.shared.serial;
|
||||||
let mut buffer = cx.shared.buffer;
|
let mut buffer = cx.shared.buffer;
|
||||||
let mut device_status = cx.shared.device_status;
|
let mut device_status = cx.shared.device_status;
|
||||||
|
let mut nvstate = cx.shared.nvstate;
|
||||||
|
let mut flash = cx.shared.flash;
|
||||||
|
|
||||||
(&mut usb_dev, &mut serial, &mut buffer, &mut device_status).lock(
|
(
|
||||||
|usb_dev, serial, buffer, device_status| {
|
&mut usb_dev,
|
||||||
usb_poll(usb_dev, serial, buffer, device_status);
|
&mut serial,
|
||||||
},
|
&mut buffer,
|
||||||
);
|
&mut device_status,
|
||||||
|
&mut nvstate,
|
||||||
|
&mut flash,
|
||||||
|
)
|
||||||
|
.lock(|usb_dev, serial, buffer, device_status, nvstate, flash| {
|
||||||
|
usb_poll(usb_dev, serial, buffer, device_status, nvstate, flash);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[task(binds = USB_LP_CAN_RX0, shared = [usb_dev, serial, buffer, device_status])]
|
#[task(binds = USB_LP_CAN_RX0, shared = [usb_dev, serial, buffer, device_status, nvstate, flash])]
|
||||||
fn usb_rx0(cx: usb_rx0::Context) {
|
fn usb_rx0(cx: usb_rx0::Context) {
|
||||||
let mut usb_dev = cx.shared.usb_dev;
|
let mut usb_dev = cx.shared.usb_dev;
|
||||||
let mut serial = cx.shared.serial;
|
let mut serial = cx.shared.serial;
|
||||||
let mut buffer = cx.shared.buffer;
|
let mut buffer = cx.shared.buffer;
|
||||||
let mut device_status = cx.shared.device_status;
|
let mut device_status = cx.shared.device_status;
|
||||||
|
let mut nvstate = cx.shared.nvstate;
|
||||||
|
let mut flash = cx.shared.flash;
|
||||||
|
|
||||||
(&mut usb_dev, &mut serial, &mut buffer, &mut device_status).lock(
|
(
|
||||||
|usb_dev, serial, buffer, device_status| {
|
&mut usb_dev,
|
||||||
usb_poll(usb_dev, serial, buffer, device_status);
|
&mut serial,
|
||||||
},
|
&mut buffer,
|
||||||
);
|
&mut device_status,
|
||||||
|
&mut nvstate,
|
||||||
|
&mut flash,
|
||||||
|
)
|
||||||
|
.lock(|usb_dev, serial, buffer, device_status, nvstate, flash| {
|
||||||
|
usb_poll(usb_dev, serial, buffer, device_status, nvstate, flash);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn usb_poll<B: usb_device::bus::UsbBus>(
|
fn usb_poll<B: usb_device::bus::UsbBus>(
|
||||||
|
@ -401,6 +425,8 @@ mod app {
|
||||||
serial: &mut usbd_serial::SerialPort<'static, B>,
|
serial: &mut usbd_serial::SerialPort<'static, B>,
|
||||||
buffer: &mut Vec<u8, USB_BUFFER_SIZE>,
|
buffer: &mut Vec<u8, USB_BUFFER_SIZE>,
|
||||||
device_status: &StatusMessage,
|
device_status: &StatusMessage,
|
||||||
|
nvstate: &mut NVState,
|
||||||
|
flash: &mut flash::Parts,
|
||||||
) {
|
) {
|
||||||
if !usb_dev.poll(&mut [serial]) {
|
if !usb_dev.poll(&mut [serial]) {
|
||||||
return;
|
return;
|
||||||
|
@ -433,7 +459,11 @@ 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::SetPLLSettings(settings) => {
|
||||||
|
nvstate.pll_settings = settings;
|
||||||
|
nvstate.save(flash);
|
||||||
|
}
|
||||||
|
HostMessage::GetPllSettings => {
|
||||||
defmt::error!("PLL output is not implemented yet")
|
defmt::error!("PLL output is not implemented yet")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use cheapsdo_protocol::PLLSettings;
|
||||||
use postcard::{from_bytes_cobs, to_slice_cobs};
|
use postcard::{from_bytes_cobs, to_slice_cobs};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use stm32f1xx_hal::flash::{self, FlashSize, FlashWriter, SectorSize};
|
use stm32f1xx_hal::flash::{self, FlashSize, FlashWriter, SectorSize};
|
||||||
|
@ -5,11 +6,15 @@ use stm32f1xx_hal::flash::{self, FlashSize, FlashWriter, SectorSize};
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub struct NVState {
|
pub struct NVState {
|
||||||
pub pwm: u16,
|
pub pwm: u16,
|
||||||
|
pub pll_settings: PLLSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for NVState {
|
impl Default for NVState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { pwm: 1500u16 }
|
Self {
|
||||||
|
pwm: 1500u16,
|
||||||
|
pll_settings: PLLSettings::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
#![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 std::io;
|
use eframe::egui::{self};
|
||||||
|
|
||||||
use std::io::Write;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use crossbeam_channel::{select, unbounded, Receiver, Sender};
|
use crossbeam_channel::{select, unbounded, Receiver, Sender};
|
||||||
|
|
||||||
use eframe::egui;
|
|
||||||
use egui_plot::{Line, Plot, PlotPoints};
|
|
||||||
|
|
||||||
use cheapsdo_protocol::*;
|
use cheapsdo_protocol::*;
|
||||||
use postcard::{from_bytes_cobs, to_stdvec_cobs};
|
|
||||||
|
|
||||||
mod formatters;
|
mod formatters;
|
||||||
|
mod plots;
|
||||||
|
mod serial;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
fn main() -> Result<(), eframe::Error> {
|
fn main() -> Result<(), eframe::Error> {
|
||||||
let options = eframe::NativeOptions {
|
let options = eframe::NativeOptions {
|
||||||
|
@ -27,26 +23,23 @@ fn main() -> Result<(), eframe::Error> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
enum SerialPortCmd {
|
|
||||||
Disconnect,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
enum SerialPortData {
|
|
||||||
DeviceState(StatusMessage),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CheapsdoControl {
|
struct CheapsdoControl {
|
||||||
serial_device: String,
|
serial_device: String,
|
||||||
serial_connected: bool,
|
serial_connected: bool,
|
||||||
|
|
||||||
data_rx: Option<Receiver<SerialPortData>>,
|
data_rx: Option<Receiver<serial::Data>>,
|
||||||
cmd_tx: Option<Sender<SerialPortCmd>>,
|
cmd_tx: Option<Sender<serial::Cmd>>,
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -54,12 +47,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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,17 +78,20 @@ impl eframe::App for CheapsdoControl {
|
||||||
select! {
|
select! {
|
||||||
recv(data_rx) -> data => {
|
recv(data_rx) -> data => {
|
||||||
match data {
|
match data {
|
||||||
Ok(SerialPortData::DeviceState(status_msg)) => {
|
Ok(serial::Data::DeviceState(status_msg)) => {
|
||||||
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,
|
||||||
status_msg.pwm as f64
|
status_msg.pwm as f64
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
Ok(serial::Data::PLLSettings(settings)) => {
|
||||||
|
self.pll_settings = settings;
|
||||||
|
}
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -128,7 +135,7 @@ impl eframe::App for CheapsdoControl {
|
||||||
self.data_rx = Some(data_rx);
|
self.data_rx = Some(data_rx);
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
poll_device(serial_device, cmd_rx, data_tx, ctx);
|
serial::poll_device(serial_device, cmd_rx, data_tx, ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,126 +144,117 @@ impl eframe::App for CheapsdoControl {
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
if let Some(cmd_tx) = &self.cmd_tx {
|
if let Some(cmd_tx) = &self.cmd_tx {
|
||||||
cmd_tx.send(SerialPortCmd::Disconnect).unwrap();
|
cmd_tx.send(serial::Cmd::Disconnect).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.serial_connected = false;
|
self.serial_connected = false;
|
||||||
|
self.cmd_tx = None;
|
||||||
|
self.data_rx = None;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.label(
|
ui.vertical(|ui| {
|
||||||
egui::RichText::new(format!(
|
ui.vertical(|ui| ui.label(egui::RichText::new("TCXO Status").size(20.0)));
|
||||||
"Measured: {}",
|
ui.add_space(20.0);
|
||||||
formatters::frequency(
|
plots::show_freuqencies(ui, &self.device_state);
|
||||||
self.device_state.measured_frequency as f64 / 1_000_000_000.0
|
ui.add_space(20.0);
|
||||||
)
|
plots::show_plots(ui, &self.average_points, &self.pwm_points);
|
||||||
))
|
});
|
||||||
.family(egui::FontFamily::Monospace)
|
|
||||||
.size(20.0),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.add_space(100.0);
|
ui.separator();
|
||||||
|
|
||||||
ui.label(
|
ui.vertical(|ui| {
|
||||||
egui::RichText::new(format!(
|
ui.label(egui::RichText::new("Output Settings").size(20.0));
|
||||||
"Average: {}",
|
ui.add_space(20.0);
|
||||||
formatters::frequency(
|
egui::Grid::new("output_pll_settings")
|
||||||
self.device_state.average_frequency as f64 / 1_000_000_000.0
|
.spacing([20.0, 5.0])
|
||||||
)
|
.show(ui, |ui| {
|
||||||
))
|
ui.label("MS1 Frequency [Hz]");
|
||||||
.family(egui::FontFamily::Monospace)
|
widgets::frequency_input(
|
||||||
.size(20.0),
|
self.ms1_frequency_id,
|
||||||
);
|
&mut self.pll_settings.ms1_frequency,
|
||||||
|
&ctx,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("MS2 Frequency [Hz]");
|
||||||
|
widgets::frequency_input(
|
||||||
|
self.ms2_frequency_id,
|
||||||
|
&mut self.pll_settings.ms2_frequency,
|
||||||
|
&ctx,
|
||||||
|
ui,
|
||||||
|
);
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
self.cmd_tx.is_some(),
|
||||||
|
egui::Button::new("Apply").fill(egui::Color32::DARK_GREEN),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
if let Some(cmd_tx) = &self.cmd_tx {
|
||||||
|
cmd_tx
|
||||||
|
.send(serial::Cmd::SetPLLSetting(self.pll_settings.clone()))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_space(40.0);
|
||||||
|
if ui
|
||||||
|
.add_enabled(
|
||||||
|
self.cmd_tx.is_some(),
|
||||||
|
egui::Button::new("Reset").fill(egui::Color32::DARK_RED),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
if let Some(cmd_tx) = &self.cmd_tx {
|
||||||
|
cmd_tx.send(serial::Cmd::GetPLLSettings).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(20.0);
|
|
||||||
|
|
||||||
let average_line = Line::new(PlotPoints::new(self.average_points.clone()));
|
|
||||||
//let measured_line = Line::new(PlotPoints::new(self.measured_points.clone()));
|
|
||||||
Plot::new("frequency_plot")
|
|
||||||
.view_aspect(4.0)
|
|
||||||
.allow_zoom(false)
|
|
||||||
.allow_scroll(false)
|
|
||||||
.allow_drag(false)
|
|
||||||
.allow_boxed_zoom(false)
|
|
||||||
.y_axis_width(12)
|
|
||||||
.y_axis_formatter(|val, _, _| formatters::frequency(val))
|
|
||||||
.label_formatter(|name, value| {
|
|
||||||
format!("{}: {}", name, formatters::frequency(value.y))
|
|
||||||
})
|
|
||||||
.show(ui, |plot_ui| {
|
|
||||||
plot_ui.set_auto_bounds([true, true].into());
|
|
||||||
plot_ui.line(average_line);
|
|
||||||
//plot_ui.line(measured_line);
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(20.0);
|
|
||||||
|
|
||||||
let pwm_line = Line::new(PlotPoints::new(self.pwm_points.clone()));
|
|
||||||
Plot::new("pwm_plot")
|
|
||||||
.view_aspect(4.0)
|
|
||||||
.allow_zoom(false)
|
|
||||||
.allow_scroll(false)
|
|
||||||
.allow_drag(false)
|
|
||||||
.allow_boxed_zoom(false)
|
|
||||||
.y_axis_width(12)
|
|
||||||
.show(ui, |plot_ui| {
|
|
||||||
plot_ui.set_auto_bounds([true, true].into());
|
|
||||||
plot_ui.line(pwm_line);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_device(
|
|
||||||
port: String,
|
|
||||||
cmd_rx: Receiver<SerialPortCmd>,
|
|
||||||
data_tx: Sender<SerialPortData>,
|
|
||||||
ctx: egui::Context,
|
|
||||||
) {
|
|
||||||
let mut port = serialport::new(port, 115_200)
|
|
||||||
.timeout(Duration::from_millis(10))
|
|
||||||
.open()
|
|
||||||
.expect("Failed to open port");
|
|
||||||
|
|
||||||
let host_msg = HostMessage::RequestStatus;
|
|
||||||
let msg_bytes = to_stdvec_cobs(&host_msg).unwrap();
|
|
||||||
port.write_all(&msg_bytes).unwrap();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let mut serial_buf: Vec<u8> = vec![0; 128];
|
|
||||||
match port.read(serial_buf.as_mut_slice()) {
|
|
||||||
Ok(t) => {
|
|
||||||
serial_buf.truncate(t);
|
|
||||||
println!("Data: {:?}", serial_buf);
|
|
||||||
|
|
||||||
let dev_msg = from_bytes_cobs::<DeviceMessage>(&mut serial_buf).unwrap();
|
|
||||||
|
|
||||||
match dev_msg {
|
|
||||||
DeviceMessage::Status(status_msg) => {
|
|
||||||
data_tx
|
|
||||||
.send(SerialPortData::DeviceState(status_msg))
|
|
||||||
.unwrap();
|
|
||||||
ctx.request_repaint();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (),
|
|
||||||
Err(e) => eprintln!("{:?}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
select! {
|
|
||||||
recv(cmd_rx) -> cmd => match cmd {
|
|
||||||
Ok(SerialPortCmd::Disconnect) => return,
|
|
||||||
Err(_) => {},
|
|
||||||
},
|
|
||||||
default(Duration::from_secs(1)) => {
|
|
||||||
let host_msg = HostMessage::RequestStatus;
|
|
||||||
let msg_bytes = to_stdvec_cobs(&host_msg).unwrap();
|
|
||||||
port.write_all(&msg_bytes).unwrap();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
use eframe::egui;
|
||||||
|
use egui_plot::{Line, Plot, PlotPoints};
|
||||||
|
|
||||||
|
use crate::formatters;
|
||||||
|
|
||||||
|
use crate::StatusMessage;
|
||||||
|
|
||||||
|
pub fn show_freuqencies(ui: &mut egui::Ui, state: &StatusMessage) {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(format!(
|
||||||
|
"Measured: {}",
|
||||||
|
formatters::format_frequency_mhz(state.measured_frequency as f64 / 1000.0)
|
||||||
|
))
|
||||||
|
.family(egui::FontFamily::Monospace)
|
||||||
|
.size(20.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.add_space(100.0);
|
||||||
|
|
||||||
|
ui.label(
|
||||||
|
egui::RichText::new(format!(
|
||||||
|
"Average: {}",
|
||||||
|
formatters::format_frequency_mhz(state.average_frequency as f64 / 1000.0)
|
||||||
|
))
|
||||||
|
.family(egui::FontFamily::Monospace)
|
||||||
|
.size(20.0),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
Plot::new("frequency_plot")
|
||||||
|
.width(900.0)
|
||||||
|
.view_aspect(3.0)
|
||||||
|
.allow_zoom(false)
|
||||||
|
.allow_scroll(false)
|
||||||
|
.allow_drag(false)
|
||||||
|
.allow_boxed_zoom(false)
|
||||||
|
.y_axis_width(12)
|
||||||
|
.y_axis_formatter(|val, _, _| formatters::format_frequency_mhz(val))
|
||||||
|
.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| {
|
||||||
|
plot_ui.set_auto_bounds([true, true].into());
|
||||||
|
plot_ui.line(average_line);
|
||||||
|
//plot_ui.line(measured_line);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(20.0);
|
||||||
|
|
||||||
|
let pwm_line = Line::new(PlotPoints::new(pwm_points.clone()));
|
||||||
|
Plot::new("pwm_plot")
|
||||||
|
.width(900.0)
|
||||||
|
.view_aspect(3.0)
|
||||||
|
.allow_zoom(false)
|
||||||
|
.allow_scroll(false)
|
||||||
|
.allow_drag(false)
|
||||||
|
.allow_boxed_zoom(false)
|
||||||
|
.y_axis_width(12)
|
||||||
|
.link_cursor("plots", true, false)
|
||||||
|
.show(ui, |plot_ui| {
|
||||||
|
plot_ui.set_auto_bounds([true, true].into());
|
||||||
|
plot_ui.line(pwm_line);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
use std::io;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crossbeam_channel::{select, Receiver, Sender};
|
||||||
|
use eframe::egui;
|
||||||
|
use postcard::{from_bytes_cobs, to_stdvec_cobs};
|
||||||
|
|
||||||
|
use cheapsdo_protocol::*;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum Cmd {
|
||||||
|
Disconnect,
|
||||||
|
GetPLLSettings,
|
||||||
|
SetPLLSetting(PLLSettings),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum Data {
|
||||||
|
DeviceState(StatusMessage),
|
||||||
|
PLLSettings(PLLSettings),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_device(port: String, cmd_rx: Receiver<Cmd>, data_tx: Sender<Data>, ctx: egui::Context) {
|
||||||
|
let mut port = serialport::new(port, 115_200)
|
||||||
|
.timeout(Duration::from_millis(10))
|
||||||
|
.open()
|
||||||
|
.expect("Failed to open port");
|
||||||
|
|
||||||
|
let get_pll_settings = HostMessage::GetPllSettings;
|
||||||
|
let get_pll_settings_bytes = to_stdvec_cobs(&get_pll_settings).unwrap();
|
||||||
|
port.write_all(&get_pll_settings_bytes).unwrap();
|
||||||
|
|
||||||
|
let request_status = HostMessage::RequestStatus;
|
||||||
|
let request_status_bytes = to_stdvec_cobs(&request_status).unwrap();
|
||||||
|
port.write_all(&request_status_bytes).unwrap();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut serial_buf: Vec<u8> = vec![0; 128];
|
||||||
|
match port.read(serial_buf.as_mut_slice()) {
|
||||||
|
Ok(t) => {
|
||||||
|
serial_buf.truncate(t);
|
||||||
|
println!("Data: {:?}", serial_buf);
|
||||||
|
|
||||||
|
let dev_msg = from_bytes_cobs::<DeviceMessage>(&mut serial_buf).unwrap();
|
||||||
|
|
||||||
|
match dev_msg {
|
||||||
|
DeviceMessage::Status(status_msg) => {
|
||||||
|
data_tx.send(Data::DeviceState(status_msg)).unwrap();
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
DeviceMessage::PLLSettings(settings) => {
|
||||||
|
data_tx.send(Data::PLLSettings(settings)).unwrap();
|
||||||
|
ctx.request_repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ref e) if e.kind() == io::ErrorKind::TimedOut => (),
|
||||||
|
Err(e) => eprintln!("{:?}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
select! {
|
||||||
|
recv(cmd_rx) -> cmd => match cmd {
|
||||||
|
Ok(Cmd::Disconnect) => return,
|
||||||
|
Ok(Cmd::GetPLLSettings) => {
|
||||||
|
port.write_all(&get_pll_settings_bytes).unwrap();
|
||||||
|
}
|
||||||
|
Ok(Cmd::SetPLLSetting(settings)) => {
|
||||||
|
let set_settings = HostMessage::SetPLLSettings(settings);
|
||||||
|
let set_settings_bytes = to_stdvec_cobs(&set_settings).unwrap();
|
||||||
|
port.write_all(&set_settings_bytes).unwrap();
|
||||||
|
}
|
||||||
|
Err(_) => {},
|
||||||
|
},
|
||||||
|
default(Duration::from_secs(1)) => {
|
||||||
|
port.write_all(&request_status_bytes).unwrap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -5,12 +5,14 @@ use serde::{Deserialize, Serialize};
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub enum DeviceMessage {
|
pub enum DeviceMessage {
|
||||||
Status(StatusMessage),
|
Status(StatusMessage),
|
||||||
|
PLLSettings(PLLSettings),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
pub enum HostMessage {
|
pub enum HostMessage {
|
||||||
RequestStatus,
|
RequestStatus,
|
||||||
SetPLLOutputs,
|
SetPLLSettings(PLLSettings),
|
||||||
|
GetPllSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
|
||||||
|
@ -29,3 +31,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,
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue