Implement simple GCode stage.

Easy experimenting with 3D printer or other CNC if
open flexure is not available.

Signed-off-by: Henner Zeller <h.zeller@acm.org>
This commit is contained in:
Henner Zeller 2026-03-08 14:04:15 +01:00
parent 4f620ad899
commit ad9b1cb8c2
4 changed files with 72 additions and 9 deletions

View file

@ -0,0 +1,45 @@
use crate::stage_io::StageIO;
use crate::xy_stage::{Result, XYStage};
pub struct GCodeStage {
stage_io: StageIO,
max_range_μm: nalgebra::Vector3<f64>,
}
impl GCodeStage {
pub fn new(stage_io: StageIO) -> Result<Self> {
let mut res = Self {
stage_io,
// TODO: determine max range with some M-code ?
max_range_μm: nalgebra::vector![100.0 * 1000.0, 100.0 * 1000.0, 50.0 * 1000.0],
};
res.stage_io.send_request("G28\n")?;
Ok(res)
}
}
impl XYStage for GCodeStage {
/// Move to absolute coordinates (x,y,z) in micrometers.
fn move_absolute_cartesian(&mut self, a: nalgebra::Vector3<f64>) -> Result<()> {
// TODO: check range
self.stage_io.send_request(format!(
"G1 X{} Y{} Z{}\n",
a[0] / 1000.0,
a[1] / 1000.0,
a[2] / 1000.0
))?;
Ok(())
}
/// Get range of valid cartesian coordinates box. For now, assuming one
/// corner is fixed at (0,0,0), the other remote corner is this range.
fn get_range(&self) -> nalgebra::Vector3<f64> {
self.max_range_μm
}
}
impl Drop for GCodeStage {
fn drop(&mut self) {
self.stage_io.send_request("M84\n").unwrap(); // switch off motors.
}
}

View file

@ -1,6 +1,7 @@
use clap::Parser;
use clap::{Parser, ValueEnum};
use std::{thread::sleep, time::Duration};
mod gcode_stage;
mod openflexure_stage;
mod stage_io;
@ -19,19 +20,37 @@ struct CliArgs {
/// Speed of the stage device (bps)
#[arg(long, default_value = "115200")]
tty_speed: u32,
#[arg(long, value_enum)]
backend: Backend,
}
// [derive(DebugCopy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
#[derive(Debug, Clone, ValueEnum)]
enum Backend {
/// OpenFlexure stage
OpenFlexure,
/// GCode stage
GCode,
}
fn main() {
let args = CliArgs::parse();
env_logger::init();
let stage_io = StageIO::new(&args.stage_device, args.tty_speed).unwrap();
let mut s = openflexure_stage::OpenFlexureStage::new(stage_io, 1.12).unwrap();
let mut stage: Box<dyn XYStage> = match args.backend {
Backend::GCode => Box::new(gcode_stage::GCodeStage::new(stage_io).unwrap()),
Backend::OpenFlexure => {
Box::new(openflexure_stage::OpenFlexureStage::new(stage_io, 1.12).unwrap())
}
};
let origin = nalgebra::vector![0.0, 0.0, 0.0];
s.move_absolute_cartesian(origin).unwrap();
stage.move_absolute_cartesian(origin).unwrap();
sleep(Duration::from_secs(1));
let mut max_xy = s.get_range();
let mut max_xy = stage.get_range();
max_xy[2] = 0.0;
s.move_absolute_cartesian(max_xy).unwrap();
stage.move_absolute_cartesian(max_xy).unwrap();
sleep(Duration::from_secs(5));
}

View file

@ -1,7 +1,7 @@
//! Stage controller for openflexure-delta like stage controllers.
use crate::stage_io::StageIO;
use crate::xy_stage::{XYStage, StageError,Result};
use crate::xy_stage::{Result, StageError, XYStage};
fn new_camera_rotation(theta: f64) -> nalgebra::Matrix3<f64> {
nalgebra::matrix![
@ -93,7 +93,7 @@ impl OpenFlexureStage {
impl XYStage for OpenFlexureStage {
fn move_absolute_cartesian(&mut self, a: nalgebra::Vector3<f64>) -> Result<()> {
// TODO: check range with get_movement_box()
// TODO: check range with get_movement_box()
let current = self.get_position_delta();
let target = self.cartesian_to_delta * a;
let target = nalgebra::vector![target[0] as i32, target[1] as i32, target[2] as i32];
@ -114,6 +114,6 @@ impl XYStage for OpenFlexureStage {
}
fn get_range(&self) -> nalgebra::Vector3<f64> {
nalgebra::vector![10000.0, 10000.0, 5000.0]
nalgebra::vector![10000.0, 10000.0, 5000.0]
}
}

View file

@ -1,4 +1,3 @@
use crate::stage_io::StageIOError;
use thiserror::Error;