// https://github.com/robert-budde/iHSV-Servo-Tool/blob/master/iHSV_Properties.py use core::fmt::Debug; use core::task::Poll; use defmt::Format; use embedded_hal::serial::{Read, Write}; use heapless::{Deque, Vec}; use num::abs; use super::Actuator; use crate::error::Error; use crate::modbus::{ModbusSerial, ModbusSerialError, ModbusSerialErrorAlias}; use crate::util::{i16_to_u16, u16_to_i16}; static ACCELERATION_IN_MS_PER_1000_RPM: u16 = 10_000; #[derive(Clone, Copy, Debug, Format)] pub enum SpindleAction { Set { status: SpindleStatus }, } #[derive(Clone, Copy, Debug, Format, PartialEq)] pub enum SpindleStatus { Off, On { rpm: i16 }, } pub trait SpindleDriver { type Error: Error; fn set(&mut self, status: SpindleStatus); fn poll(&mut self) -> Poll>; } #[derive(Clone, Copy, Debug, Format)] enum JmcHsv57ModbusRequest { InitControlMode, InitSpeedSource, InitAcceleration { ms_per_1000_rpm: u16 }, InitDeceleration { ms_per_1000_rpm: u16 }, SetSpeed { rpm: u16 }, GetSpeed, } #[derive(Clone, Copy, Debug, Format)] enum JmcHsv57ModbusResponseType { InitControlMode, InitSpeedSource, InitAcceleration, InitDeceleration, SetSpeed, GetSpeed, } pub struct SpindleDriverJmcHsv57 where Serial: Write + Read, { modbus: ModbusSerial, modbus_requests: Deque, modbus_response_type: Option, has_initialized: bool, spindle_status: SpindleStatus, next_spindle_status: Option, current_rpm: Option, } const RPM_ERROR_BOUND: i16 = 2; impl SpindleDriverJmcHsv57 where Serial: Write + Read, >::Error: Debug, >::Error: Debug, { pub fn new(serial: Serial) -> Self { Self { modbus: ModbusSerial::new(serial, 1), modbus_requests: Deque::new(), modbus_response_type: None, has_initialized: false, spindle_status: SpindleStatus::Off, next_spindle_status: None, current_rpm: None, } } // https://www.makerstore.com.au/download/software/Manual-of-IHSV-Integrated-servo-motor.pdf pub fn handle_modbus(&mut self) -> Poll>> { match self.modbus.poll() { Poll::Ready(Ok(is_response_ready)) => { if is_response_ready { // handle modbus response match self.modbus_response_type.unwrap() { JmcHsv57ModbusResponseType::InitControlMode => self.modbus.parse_ok()?, JmcHsv57ModbusResponseType::InitSpeedSource => self.modbus.parse_ok()?, JmcHsv57ModbusResponseType::InitAcceleration => self.modbus.parse_ok()?, JmcHsv57ModbusResponseType::InitDeceleration => self.modbus.parse_ok()?, JmcHsv57ModbusResponseType::SetSpeed => self.modbus.parse_ok()?, JmcHsv57ModbusResponseType::GetSpeed => { let mut result: Vec = Vec::new(); self.modbus.parse_u16(&mut result)?; let rpm_in_u16 = result.get(0).unwrap(); let rpm_in_i16 = u16_to_i16(*rpm_in_u16); self.current_rpm = Some(rpm_in_i16); } }; return Poll::Pending; } else { if !self.modbus_requests.is_empty() { // setup next modbus request match self.modbus_requests.pop_front().unwrap() { JmcHsv57ModbusRequest::InitControlMode => { // set P01-01 (0x0065) to 1 self.modbus_response_type = Some(JmcHsv57ModbusResponseType::InitControlMode); self.modbus.set_holding(0x0065, 1)?; } JmcHsv57ModbusRequest::InitSpeedSource => { // set P04-01 (0x0191) to 1 self.modbus_response_type = Some(JmcHsv57ModbusResponseType::InitSpeedSource); self.modbus.set_holding(0x0191, 1)?; } JmcHsv57ModbusRequest::InitAcceleration { ms_per_1000_rpm } => { // set P04-14 (0x019E) to unit 1ms/1000rpm self.modbus_response_type = Some(JmcHsv57ModbusResponseType::InitAcceleration); self.modbus.set_holding(0x019E, ms_per_1000_rpm)?; } JmcHsv57ModbusRequest::InitDeceleration { ms_per_1000_rpm } => { // set P04-15 (0x019F) to unit 1ms/1000rpm self.modbus_response_type = Some(JmcHsv57ModbusResponseType::InitDeceleration); self.modbus.set_holding(0x019F, ms_per_1000_rpm)?; } JmcHsv57ModbusRequest::SetSpeed { rpm } => { // set P04-02 (0x0192) to rpm (-6000 to 6000) self.modbus_response_type = Some(JmcHsv57ModbusResponseType::SetSpeed); self.modbus.set_holding(0x0192, rpm)?; } JmcHsv57ModbusRequest::GetSpeed => { // get d08.F.SP (0x0842) for rpm self.modbus_response_type = Some(JmcHsv57ModbusResponseType::GetSpeed); self.modbus.get_holdings(0x0842, 1)?; } }; Poll::Pending } else { Poll::Ready(Ok(())) } } } Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Pending => Poll::Pending, } } fn desired_rpm(&mut self) -> i16 { match self.spindle_status { SpindleStatus::On { rpm } => rpm, SpindleStatus::Off => 0, } } } #[derive(Debug)] pub enum SpindleDriverJmcHsv57Error { ModbusSerial(ModbusSerialError), QueueFull, } type SpindleDriverJmcHsv57ErrorAlias = SpindleDriverJmcHsv57Error<>::Error, >::Error>; impl SpindleDriver for SpindleDriverJmcHsv57 where Serial: Write + Read, >::Error: Debug, >::Error: Debug, { type Error = SpindleDriverJmcHsv57ErrorAlias; fn set(&mut self, status: SpindleStatus) { self.next_spindle_status = Some(status); } fn poll(&mut self) -> Poll> { match self.handle_modbus() { Poll::Pending => return Poll::Pending, Poll::Ready(Err(err)) => { return Poll::Ready(Err(SpindleDriverJmcHsv57Error::ModbusSerial(err))) } Poll::Ready(Ok(())) => { // pass through } } if !self.has_initialized { // initialize spindle over modbus self.modbus_requests .push_back(JmcHsv57ModbusRequest::InitControlMode) .map_err(|_| SpindleDriverJmcHsv57Error::QueueFull)?; self.modbus_requests .push_back(JmcHsv57ModbusRequest::InitSpeedSource) .map_err(|_| SpindleDriverJmcHsv57Error::QueueFull)?; self.modbus_requests .push_back(JmcHsv57ModbusRequest::InitAcceleration { ms_per_1000_rpm: ACCELERATION_IN_MS_PER_1000_RPM, }) .map_err(|_| SpindleDriverJmcHsv57Error::QueueFull)?; self.modbus_requests .push_back(JmcHsv57ModbusRequest::InitDeceleration { ms_per_1000_rpm: ACCELERATION_IN_MS_PER_1000_RPM, }) .map_err(|_| SpindleDriverJmcHsv57Error::QueueFull)?; self.has_initialized = true; return Poll::Pending; } if let Some(next_spindle_status) = self.next_spindle_status { if next_spindle_status != self.spindle_status { self.spindle_status = next_spindle_status; // set speed over modbus let rpm_in_i16 = self.desired_rpm(); let rpm_in_u16 = i16_to_u16(rpm_in_i16); self.modbus_requests .push_back(JmcHsv57ModbusRequest::SetSpeed { rpm: rpm_in_u16 }) .map_err(|_| SpindleDriverJmcHsv57Error::QueueFull)?; return Poll::Pending; } } // check if speed has been reached if let Some(current_rpm) = self.current_rpm { // if rpm within error bounds let desired_rpm = self.desired_rpm(); if abs(current_rpm - desired_rpm) < RPM_ERROR_BOUND { return Poll::Ready(Ok(())); } } // get speed over modbus self.modbus_requests .push_back(JmcHsv57ModbusRequest::GetSpeed) .map_err(|_| SpindleDriverJmcHsv57Error::QueueFull)?; return Poll::Pending; } } pub struct SpindleDevice where Driver: SpindleDriver, { driver: Driver, } impl SpindleDevice where Driver: SpindleDriver, { pub fn new(driver: Driver) -> Self { Self { driver } } } pub type SpindleError = ::Error; impl Actuator for SpindleDevice where Driver: SpindleDriver, { type Action = SpindleAction; type Error = Driver::Error; fn run(&mut self, action: &Self::Action) { match action { SpindleAction::Set { status } => self.driver.set(*status), } } fn poll(&mut self) -> Poll> { self.driver.poll() } }