Hi all. I would appreciate some help with my Rust MCP3002 SPI based Raspberry Pi 5 oscilloscope, the code for which is below. My program/code has 2 threads, one for reading the SPI output from the MCP3002, and one for rendering the oscilloscope trace. Prior to my using threads, I ran the code using separate loops for the SPI output and the trace rendering, and the code worked, but without using threads, both the SPI output and the oscilloscope trace were painfully slow! Here is the issue the compiler responds with : error[E0277]: dyn PlotItem
cannot be sent between threads safely
--> src/main.rs:210:37
|
210 | let handle2 = thread::spawn({
| __________-------------^
| | |
| | required by a bound introduced by this call
211 | |
212 | |/ move || {
213 | || let locked_array2 = array3.lock().unwrap();
214 | || unsafe {
... ||
262 | || } //closes the unsafe loop
263 | || } //closes the move loop
| ||- this tail expression is of type {closure@main.rs:212:10}
264 | | }); //closes the thread::spawn
| |^ dyn PlotItem
cannot be sent between threads safely
|
= help: the trait Send
is not implemented for dyn PlotItem
I've tried to solve the problem unsuccessfully and online information suggests making PlotItem "+ Send" but I'm not sure how to do this. My code is below, if anyone would like the full program inc cargo toml I'd be happy to let them have it. This has been my first Rust project, developed initially from the Cistercian Clock on github, which itself is a great project and very very helpful for beginners like me! Here's my code, many thanks in anticipation, Paul Clements.
use egui_plot::{AxisHints, Line, PlotBounds, PlotItem, PlotPoint, PlotPoints, Text};
//use egui_plot;
//use std::sync::{Arc, RwLock};
use std::thread;
use std::sync::{Arc, Mutex};
use std::sync::mpsc;
//use egcd /home/pi/.cargo/registry/src/index.crates.io-6f17d22bba15001f/egui_plot-0.31.0/ui::Color32;
use rppal::spi::{Bus, Mode, SlaveSelect, Spi};
use std::time::Duration;
use std::mem;
//use crate::Plot;
//use chrono::Duration;
//use egui::{Ui, Vec2};
use egui::{
Color32,
FontFamily::Proportional,
FontId, Ui,
TextStyle::{Body, Button, Heading, Monospace, Name, Small},
};
static mut y: u32= 0;
static mut x: f64 = 0.5;
//static mut c2: f64 = 0.0;
static mut x1: f64 = 0.01; //timebase
static mut Y1: f64 = 1.0;
static mut z1: u32 = 1;//VOLTS/DIV let mut guard = vec.lock().unwrap();
static mut w1: f64 = 1.0;//TRACE VERTICAL POSITION
static mut a1: u8 = 1;
static mut j1: f64 = 0.0;
//static mut k1: u32 = 0;
static mut f: usize = 0; //updater for vector for y value
static mut f1: f64 = 0.0;
static mut G1: f64 = 0.0;
static mut g: f64 = 0.0;
static mut h: u8 = 0; // for initialising SPI loop
static mut m:f64 = 0.0;// for initialising line draw loop
//static mut array: Vec = Vec::new();
struct App{}
/*struct Colours {
colour_0: Color32,
colour_1: Color32,
colour_2: Color32,
} */
impl eframe::App for App {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { //brackets a
egui::menu::bar(ui, |ui| { //brackets b
ui.menu_button("TIMEBASE", |ui| {
unsafe {
//while h == 2
//{
if ui.button("TRACE SPEED UP").clicked() {
x1 +=0.1;}
if ui.button("TRACE REDUCE SPEED").clicked() {
x1 -=0.1;}
if x1 <= 0.001
{x1 = 0.001}
//prevents a "timebase crash"
//h = 3 }
}
});
ui.add_space(10.0);
ui.menu_button("VOLTS/DIV", |ui| {
unsafe{ if ui.button("increase sensitivity").clicked() {
z1 +=1;}
if ui.button("decrease sensitivity").clicked() {
z1 -= 1;}
if z1 <= 1 {
z1 = 1 }
}
});
ui.add_space(10.0);
ui.menu_button("TRACE POSITION", |ui| {
unsafe{ if ui.button("MOVE TRACE DOWN").clicked() {
w1 -=3.0;}
if ui.button("MOVE TRACE UP").clicked() {
w1 +=3.0;}}
});
ui.add_space(10.0);
ui.menu_button("TRACE COLOUR", |ui| {
unsafe{ if ui.button("GREEN").clicked() {
a1 =1;}
if ui.button("BLUE").clicked() {
a1 =2;}
if ui.button("RED").clicked() {
a1 =3;}
if ui.button("YELLOW").clicked() {
a1 =4;}}
});
ui.add_space(10.0);
}); });
let mut style = (*ctx.style()).clone();
style.text_styles = [
//(Heading, FontId::new(35.0, Proportional, Color32::BLUE)),
(Heading, FontId::new(35.0, Proportional)),
(Name("Scope".into()), FontId::new(64.0, Proportional)),
(Body, FontId::new(10.0, Proportional)),
(Monospace, FontId::new(10.0, Proportional)),
(Button, FontId::new(15.0, Proportional)),
(Small, FontId::new(10.0, Proportional)),
]
.into();
ctx.set_style(style.clone());
egui::CentralPanel::default().show(ctx,|ui|{
ui.heading("USB OSCILLOSCOPE/SPECTRUM ANALYSER USING EGUI_PLOT, WRITTEN IN RUST");
unsafe {
let h2 = h.to_string();
ui.label("h =");
ui.label(h2);
ui.add_space(5.0);
ui.label("x =");
let x4 = x.to_string();
ui.label(x4);
ui.label(" trace val =");
//let c2 = y.to_string();
//ui.label(c2);
}
//=====================================================
//SPI MCP3002 DRIVER THREAD
let array = Arc::new(Mutex::new(Vec::new()));// wrap the array of spi readings in a mutex
let array2 = Arc::clone(&array);
let array3 = Arc::clone(&array);
unsafe {
let E1 = 150.0/x1; //E1 is the number of mini line segments which make up the trace
//let mut array = Vec::with_capacity(150000);
let handle1 = thread::spawn({
move || {
let mut locked_array2 = array2.lock().unwrap();
x+= x1;
let mut spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 100000, Mode::Mode0);
// Data to send
let senddata = [0x0D, 0x02];
let mut readdata = [4; 2];
let _ = spi.as_mut().expect("Result::Err").transfer(&mut readdata, &senddata);
//let dog1 =readdata[1].to_string();
j1 = readdata[1] as f64 + w1;
let j2 = j1 as u32;
y = z1*j2;
locked_array2.push(y);
let y4 = y.to_string();
ui.label(y4);
if x >= 150.0
{ x = 0.5;
h = 1;
}
}
});
}
//END OF SPI MCP3002 THREAD
//===========================================================
// Define x-axis hints
let x_axis = vec![AxisHints::new_x().label("Time (millisecs)")]; //reinstate if prob
let temp_y_axis = AxisHints::new_y().label("voltage").max_digits(4);
let y_axis = vec! [temp_y_axis];
ui.ctx().request_repaint_after(Duration::from_millis(1));
egui_plot::Plot::new("RUSTY SCOPE")
//.legend(Legend::default--allow-dirty
.custom_x_axes(x_axis)
.custom_y_axes(y_axis)
.show(ui, |plot_ui|
{plot_ui.set_plot_bounds(PlotBounds::from_min_max(
[0.0, -130.0], [150.0, 130.0], ));
plot_ui.set_auto_bounds(false);
//======================================================
// DRAW OSCILLOSCOPE TRACE THREAD
let handle2 = thread::spawn({
move || {
let locked_array2 = array3.lock().unwrap();
unsafe {
let E1:f64 = 150.0/x1;
//let y2 = array[f];
let y2 = locked_array2[f];
let y5 = y2.to_string();
//let ui2 = &mut Ui;
Text::new([20.0, 20.0].into(), y5);
x += x1;
f += 1; //increment the array counter
let y3 = y2 as f64;
let mut data: Vec<[f64; 2]> = vec![[x, y3],[x+x1, y3]];
x+= x1;
//let points: PlotPoints = data.clone().into_iter().collect();
let points: PlotPoints = data.clone().into();
if a1 == 1
{let scopetrace = Line::new(points).color(Color32::GREEN);
plot_ui.line(scopetrace);
}
if a1 == 2
{let scopetrace = Line::new(points);
plot_ui.line(scopetrace);
}
if a1 == 3
{let scopetrace = Line::new(points).color(Color32::ORANGE);
plot_ui.line(scopetrace);
}
if a1 == 4
{let scopetrace = Line::new(points).color(Color32::YELLOW);
plot_ui.line(scopetrace);
}
data.clear();
if x >= 150.0
//if m > E1
{ m = 0.0;
locked_array2.clear();
f = 0;
x = 0.5;
h = 0; //go back to reading the SPI
}; //return trace to start at and of scan
if z1 >= 100
{z1 = 100}; //ENSURES MAX TRACE MAG OF 100
} //closes the unsafe loop
} //closes the move loop
}); //closes the thread::spawn
}); //closes the show plot_ui
}); //closes the central panel
//=========================================================
} //END OF DRAW OSCILLOSCOPE TRACE THREAD
handle1.join().unwrap;
handle2.join().unwrap()
}
fn main()-> eframe::Result {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default(),
/* .with_inner_size([3000.0, 1500.0])
.with_min_inner_size([2000.0,1500.0]),
*/
/*.with_icon(
// NOTE: Adding an icon is optional
eframe::icon_data::from_png_bytes(&include_bytes!("../assets/icon-256.png")[..])
.expect("Failed to load icon"),
), */
..Default::default()
};
eframe::run_native(
" PAUL'S EXPERIMENTAL RUST OSCILLOSCOPE USING EGUI_PLOT",
native_options,
Box::new(|cc| Ok(Box::new(App{}))),
)
}