autocore_std/motion/
sim.rs1use super::axis_view::AxisView;
28use super::cia402::Cia402State;
29
30fn status_base(state: Cia402State) -> u16 {
33 match state {
34 Cia402State::NotReadyToSwitchOn => 0x0000,
35 Cia402State::SwitchOnDisabled => 0x0040,
36 Cia402State::ReadyToSwitchOn => 0x0021,
37 Cia402State::SwitchedOn => 0x0023,
38 Cia402State::OperationEnabled => 0x0027,
39 Cia402State::QuickStopActive => 0x0007,
40 Cia402State::FaultReactionActive => 0x000F,
41 Cia402State::Fault => 0x0008,
42 Cia402State::Unknown => 0x0000,
43 }
44}
45
46#[derive(Debug, Clone)]
53pub struct SimDrive {
54 control_word: u16,
56 target_position: i32,
57 profile_velocity: u32,
58 profile_acceleration: u32,
59 profile_deceleration: u32,
60 modes_of_operation: i8,
61
62 status_word: u16,
64 position_actual: i32,
65 velocity_actual: i32,
66 modes_display: i8,
67
68 state: Cia402State,
70 active_target: i32,
71 moving: bool,
72 setpoint_ack: bool,
73 prev_new_setpoint: bool,
74
75 halt_blocks_setpoint_ack: bool,
80}
81
82impl Default for SimDrive {
83 fn default() -> Self {
84 Self {
85 control_word: 0,
86 target_position: 0,
87 profile_velocity: 0,
88 profile_acceleration: 0,
89 profile_deceleration: 0,
90 modes_of_operation: 0,
91 status_word: status_base(Cia402State::SwitchOnDisabled) | (1 << 9),
92 position_actual: 0,
93 velocity_actual: 0,
94 modes_display: 0,
95 state: Cia402State::SwitchOnDisabled,
96 active_target: 0,
97 moving: false,
98 setpoint_ack: false,
99 prev_new_setpoint: false,
100 halt_blocks_setpoint_ack: false,
101 }
102 }
103}
104
105impl SimDrive {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn set_halt_blocks_setpoint_ack(&mut self, v: bool) {
115 self.halt_blocks_setpoint_ack = v;
116 }
117
118 pub fn step(&mut self, dt: f64) {
122 self.step_state_machine();
123
124 let cw = self.control_word;
125 let bit = |n: u8| cw & (1 << n) != 0;
126 let new_setpoint = bit(4);
127 let halt = bit(8);
128
129 let enabled = self.state == Cia402State::OperationEnabled;
130 let pp_mode = self.modes_of_operation == 1;
131
132 if enabled && pp_mode {
134 if new_setpoint && !self.prev_new_setpoint {
135 if !(self.halt_blocks_setpoint_ack && halt) {
138 self.active_target = self.target_position;
139 self.setpoint_ack = true;
140 self.moving = true;
141 }
142 }
143 if !new_setpoint {
144 self.setpoint_ack = false;
146 }
147 } else {
148 self.setpoint_ack = false;
149 self.moving = false;
150 }
151 self.prev_new_setpoint = new_setpoint;
152
153 if enabled && pp_mode && !halt && self.moving {
155 let remaining = self.active_target - self.position_actual;
156 if remaining == 0 {
157 self.velocity_actual = 0;
158 self.moving = false;
159 } else {
160 let step = ((self.profile_velocity as f64 * dt).round() as i32).max(1);
161 if step >= remaining.abs() {
162 self.position_actual = self.active_target;
163 self.velocity_actual = 0;
164 self.moving = false;
165 } else {
166 let dir = remaining.signum();
167 self.position_actual += dir * step;
168 self.velocity_actual = dir * self.profile_velocity as i32;
169 }
170 }
171 } else {
172 self.velocity_actual = 0;
174 if halt {
175 self.moving = false;
176 }
177 }
178
179 let mut sw = status_base(self.state) | (1 << 9); if self.setpoint_ack {
182 sw |= 1 << 12; }
184 if !self.moving && self.position_actual == self.active_target {
185 sw |= 1 << 10; }
187 self.status_word = sw;
188 self.modes_display = self.modes_of_operation;
189 }
190
191 fn step_state_machine(&mut self) {
196 let cw = self.control_word;
197 let switch_on = cw & (1 << 0) != 0;
198 let enable_voltage = cw & (1 << 1) != 0;
199 let quick_stop = cw & (1 << 2) != 0;
200 let enable_op = cw & (1 << 3) != 0;
201
202 use Cia402State::*;
203 if !enable_voltage || !quick_stop {
205 self.state = SwitchOnDisabled;
206 return;
207 }
208 self.state = if switch_on {
210 if enable_op { OperationEnabled } else { SwitchedOn }
211 } else {
212 ReadyToSwitchOn
213 };
214 }
215}
216
217impl AxisView for SimDrive {
218 fn control_word(&self) -> u16 {
219 self.control_word
220 }
221 fn set_control_word(&mut self, word: u16) {
222 self.control_word = word;
223 }
224 fn set_target_position(&mut self, pos: i32) {
225 self.target_position = pos;
226 }
227 fn set_profile_velocity(&mut self, vel: u32) {
228 self.profile_velocity = vel;
229 }
230 fn set_profile_acceleration(&mut self, accel: u32) {
231 self.profile_acceleration = accel;
232 }
233 fn set_profile_deceleration(&mut self, decel: u32) {
234 self.profile_deceleration = decel;
235 }
236 fn set_modes_of_operation(&mut self, mode: i8) {
237 self.modes_of_operation = mode;
238 }
239 fn modes_of_operation_display(&self) -> i8 {
240 self.modes_display
241 }
242 fn status_word(&self) -> u16 {
243 self.status_word
244 }
245 fn position_actual(&self) -> i32 {
246 self.position_actual
247 }
248 fn velocity_actual(&self) -> i32 {
249 self.velocity_actual
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use crate::motion::cia402::{Cia402Control, Cia402Status, RawControlWord, RawStatusWord};
257
258 fn enable(sim: &mut SimDrive) {
261 let mut cw = RawControlWord(0);
262 cw.cmd_enable_operation(); sim.set_control_word(cw.raw());
264 sim.set_modes_of_operation(1); for _ in 0..3 {
266 sim.step(0.01);
267 }
268 }
269
270 #[test]
271 fn reaches_operation_enabled() {
272 let mut sim = SimDrive::new();
273 assert_eq!(RawStatusWord(sim.status_word()).state(), Cia402State::SwitchOnDisabled);
274 enable(&mut sim);
275 assert_eq!(RawStatusWord(sim.status_word()).state(), Cia402State::OperationEnabled);
276 }
277
278 #[test]
279 fn disable_voltage_returns_to_switch_on_disabled() {
280 let mut sim = SimDrive::new();
281 enable(&mut sim);
282 sim.set_control_word(0x0000); sim.step(0.01);
284 assert_eq!(RawStatusWord(sim.status_word()).state(), Cia402State::SwitchOnDisabled);
285 }
286
287 #[test]
290 fn pp_move_runs_handshake_and_reaches_target() {
291 let mut sim = SimDrive::new();
292 enable(&mut sim);
293
294 sim.set_target_position(1000);
295 sim.set_profile_velocity(10_000); let mut cw = RawControlWord(sim.control_word());
299 cw.set_bit(4, true);
300 sim.set_control_word(cw.raw());
301 sim.step(0.01);
302
303 let sw = RawStatusWord(sim.status_word());
304 assert!(sw.raw() & (1 << 12) != 0, "setpoint acknowledge asserted on rising edge");
305 assert!(sim.velocity_actual() > 0, "moving toward target");
306
307 cw.set_bit(4, false);
309 sim.set_control_word(cw.raw());
310 sim.step(0.01);
311 assert!(RawStatusWord(sim.status_word()).raw() & (1 << 12) == 0, "ack cleared");
312
313 for _ in 0..20 {
315 sim.step(0.01);
316 }
317 assert_eq!(sim.position_actual(), 1000);
318 assert_eq!(sim.velocity_actual(), 0);
319 assert!(RawStatusWord(sim.status_word()).raw() & (1 << 10) != 0, "target reached");
320 }
321
322 #[test]
323 fn halt_brings_velocity_to_zero() {
324 let mut sim = SimDrive::new();
325 enable(&mut sim);
326 sim.set_target_position(100_000);
327 sim.set_profile_velocity(10_000);
328 let mut cw = RawControlWord(sim.control_word());
329 cw.set_bit(4, true);
330 sim.set_control_word(cw.raw());
331 sim.step(0.01);
332 assert!(sim.velocity_actual() > 0);
333
334 cw.set_bit(8, true);
336 sim.set_control_word(cw.raw());
337 sim.step(0.01);
338 assert_eq!(sim.velocity_actual(), 0, "halt stops motion");
339 }
340
341 #[test]
345 fn quirk_emulation_blocks_ack_while_halted() {
346 let mut sim = SimDrive::new();
347 sim.set_halt_blocks_setpoint_ack(true);
348 enable(&mut sim);
349
350 let mut cw = RawControlWord(sim.control_word());
352 cw.set_bit(8, true); cw.set_bit(4, true); sim.set_control_word(cw.raw());
355 sim.step(0.01);
356 assert!(RawStatusWord(sim.status_word()).raw() & (1 << 12) == 0,
357 "quirk drive must NOT ack a setpoint while halted");
358
359 let mut ideal = SimDrive::new();
361 enable(&mut ideal);
362 ideal.set_control_word(cw.raw());
363 ideal.step(0.01);
364 assert!(RawStatusWord(ideal.status_word()).raw() & (1 << 12) != 0,
365 "ideal drive acks even while halted");
366 }
367}