1#![no_std]
8
9pub mod enums;
10mod errors;
11pub mod helpers;
12pub mod response;
13
14pub use enums::{
15 BaudRate, EnLogic, MotorType, RotationDirection, SaveClearStatus, ShaftStatus, WorkMode,
16 ZeroMode,
17};
18pub use errors::Error;
19pub use helpers::{
20 EnPinStatus, EncoderValue, MotorShaftAngle, ShaftErrValue, angle_to_steps,
21 encoder_val_to_degrees, estimate_move_duration, parse_en_pin_status_response,
22 parse_encoder_response, parse_motor_shaft_angle_error, parse_motor_shaft_angle_response,
23 parse_pulse_count_response, parse_shaft_status_response, parse_success_response,
24 speed_setting_to_rpm, strip_leading_garbage,
25};
26pub use response::{InvalidResponse, Response};
27
28pub const DEFAULT_ADDRESS: u8 = 0xE0;
30pub const MIN_ADDRESS: u8 = 0xE0;
32pub const MAX_ADDRESS: u8 = 0xE9;
34
35pub const MAX_SPEED: u8 = 0x7F;
37pub const MAX_CURRENT_INDEX: u8 = 0x0F;
39pub const MAX_SUBDIVISION: u8 = 0xFF;
41pub const MAX_ZERO_SPEED: u8 = 0x04;
43
44pub const CURRENT_STEP_MA: u16 = 200;
46
47pub const MAX_TORQUE_LIMIT: u16 = 0x4B0;
49
50const CMD_BUFFER_SIZE: usize = 10;
51
52mod cmd {
53 pub const READ_ENCODER_VALUE: u8 = 0x30;
54 pub const READ_PULSE_COUNT: u8 = 0x33;
55 pub const READ_MOTOR_SHAFT_ANGLE: u8 = 0x36;
56 pub const READ_MOTOR_SHAFT_ANGLE_ERROR: u8 = 0x39;
57 pub const READ_EN_PIN_STATUS: u8 = 0x3A;
58 pub const READ_RELEASE_STATUS: u8 = 0x3D;
59 pub const READ_SHAFT_STATUS: u8 = 0x3E;
60 pub const SAVE_CLEAR_STATUS: u8 = 0xFF;
61
62 pub const CALIBRATE_ENCODER: u8 = 0x80;
63 pub const SET_CURRENT_LIMIT: u8 = 0x83;
64 pub const SET_SUBDIVISION: u8 = 0x84;
65 pub const SET_EN_LOGIC: u8 = 0x85;
66 pub const SET_DIRECTION: u8 = 0x86;
67 pub const SET_AUTO_SCREEN_OFF: u8 = 0x87;
68 pub const SET_PROTECTION: u8 = 0x88;
69 pub const SET_INTERPOLATION: u8 = 0x89;
70
71 pub const SET_ZERO_MODE: u8 = 0x90;
72 pub const SET_CURRENT_AS_ZERO: u8 = 0x91;
73 pub const SET_ZERO_SPEED: u8 = 0x92;
74 pub const SET_ZERO_DIRECTION: u8 = 0x93;
75 pub const GO_TO_ZERO: u8 = 0x94;
76
77 pub const SET_POSITION_KP: u8 = 0xA1;
78 pub const SET_POSITION_KI: u8 = 0xA2;
79 pub const SET_POSITION_KD: u8 = 0xA3;
80 pub const SET_ACCELERATION: u8 = 0xA4;
81 pub const SET_MAX_TORQUE: u8 = 0xA5;
82
83 pub const ENABLE_MOTOR: u8 = 0xF3;
84 pub const RUN_WITH_CONSTANT_SPEED: u8 = 0xF6;
85 pub const STOP: u8 = 0xF7;
86 pub const RUN_MOTOR: u8 = 0xFD;
87}
88
89#[derive(Debug, Copy, Clone)]
94pub struct Driver {
95 address: u8,
96 buffer: [u8; CMD_BUFFER_SIZE],
97}
98
99type Result<T> = core::result::Result<T, Error>;
100
101impl Default for Driver {
102 fn default() -> Self {
104 Self {
105 address: DEFAULT_ADDRESS,
106 buffer: [0; CMD_BUFFER_SIZE],
107 }
108 }
109}
110
111impl Driver {
112 #[must_use]
114 pub fn with_address(address: u8) -> Self {
115 Self {
116 address,
117 ..Default::default()
118 }
119 }
120
121 pub fn enable_motor(&mut self, enable: bool) -> &[u8] {
123 self.build_command(&[self.address, cmd::ENABLE_MOTOR, u8::from(enable)])
124 }
125
126 pub fn run_with_constant_speed(
131 &mut self,
132 direction: RotationDirection,
133 speed: u8,
134 ) -> Result<&[u8]> {
135 if speed > MAX_SPEED {
136 return Err(Error::InvalidValue);
137 }
138 let dir_mask = match direction {
139 RotationDirection::Clockwise => 0x00,
140 RotationDirection::CounterClockwise => 0x80,
141 };
142 Ok(self.build_command(&[self.address, cmd::RUN_WITH_CONSTANT_SPEED, speed | dir_mask]))
143 }
144
145 pub fn stop(&mut self) -> &[u8] {
147 self.build_command(&[self.address, cmd::STOP])
148 }
149
150 pub fn save_clear_status(&mut self, operation: SaveClearStatus) -> &[u8] {
155 self.build_command(&[self.address, cmd::SAVE_CLEAR_STATUS, operation as u8])
156 }
157
158 pub fn run_motor(
163 &mut self,
164 direction: RotationDirection,
165 speed: u8,
166 pulses: u32,
167 ) -> Result<&[u8]> {
168 if speed > MAX_SPEED {
169 return Err(Error::InvalidValue);
170 }
171 let dir_mask = match direction {
172 RotationDirection::Clockwise => 0x00,
173 RotationDirection::CounterClockwise => 0x80,
174 };
175 let pulse_bytes = pulses.to_be_bytes();
176 Ok(self.build_command(&[
177 self.address,
178 cmd::RUN_MOTOR,
179 speed | dir_mask,
180 pulse_bytes[0], pulse_bytes[1], pulse_bytes[2], pulse_bytes[3], ]))
185 }
186
187 pub fn calibrate_encoder(&mut self) -> &[u8] {
189 self.build_command(&[self.address, cmd::CALIBRATE_ENCODER, 0x00])
190 }
191
192 pub fn set_current_limit(&mut self, index: u8) -> Result<&[u8]> {
197 if index > MAX_CURRENT_INDEX {
198 return Err(Error::InvalidValue);
199 }
200 Ok(self.build_command(&[self.address, cmd::SET_CURRENT_LIMIT, index]))
201 }
202
203 pub fn set_subdivision(&mut self, subdivision: u8) -> Result<&[u8]> {
206 Ok(self.build_command(&[self.address, cmd::SET_SUBDIVISION, subdivision]))
207 }
208
209 pub fn set_enable_logic(&mut self, logic: EnLogic) -> &[u8] {
211 self.build_command(&[self.address, cmd::SET_EN_LOGIC, logic as u8])
212 }
213
214 pub fn set_direction(&mut self, direction: RotationDirection) -> &[u8] {
216 self.build_command(&[self.address, cmd::SET_DIRECTION, direction as u8])
217 }
218
219 pub fn set_auto_screen_off(&mut self, enable: bool) -> &[u8] {
221 self.build_command(&[self.address, cmd::SET_AUTO_SCREEN_OFF, u8::from(!enable)])
222 }
223
224 pub fn set_stall_protection(&mut self, enable: bool) -> &[u8] {
226 self.build_command(&[self.address, cmd::SET_PROTECTION, u8::from(!enable)])
227 }
228
229 pub fn set_interpolation(&mut self, enable: bool) -> &[u8] {
231 self.build_command(&[self.address, cmd::SET_INTERPOLATION, u8::from(!enable)])
232 }
233
234 pub fn set_zero_mode(&mut self, mode: ZeroMode) -> &[u8] {
236 self.build_command(&[self.address, cmd::SET_ZERO_MODE, mode as u8])
237 }
238
239 pub fn set_current_as_zero(&mut self) -> &[u8] {
241 self.build_command(&[self.address, cmd::SET_CURRENT_AS_ZERO, 0x00])
242 }
243
244 pub fn set_zero_speed(&mut self, speed: u8) -> Result<&[u8]> {
249 if speed > MAX_ZERO_SPEED {
250 return Err(Error::InvalidValue);
251 }
252 Ok(self.build_command(&[self.address, cmd::SET_ZERO_SPEED, speed]))
253 }
254
255 pub fn go_to_zero(&mut self) -> &[u8] {
257 self.build_command(&[self.address, cmd::GO_TO_ZERO, 0x00])
258 }
259
260 pub fn set_zero_direction(&mut self, direction: RotationDirection) -> &[u8] {
262 self.build_command(&[self.address, cmd::SET_ZERO_DIRECTION, direction as u8])
263 }
264
265 pub fn set_position_kp(&mut self, value: u16) -> &[u8] {
267 let bytes = value.to_be_bytes();
268 self.build_command(&[self.address, cmd::SET_POSITION_KP, bytes[0], bytes[1]])
269 }
270
271 pub fn set_position_ki(&mut self, value: u16) -> &[u8] {
273 let bytes = value.to_be_bytes();
274 self.build_command(&[self.address, cmd::SET_POSITION_KI, bytes[0], bytes[1]])
275 }
276
277 pub fn set_position_kd(&mut self, value: u16) -> &[u8] {
279 let bytes = value.to_be_bytes();
280 self.build_command(&[self.address, cmd::SET_POSITION_KD, bytes[0], bytes[1]])
281 }
282
283 pub fn set_acceleration(&mut self, value: u16) -> &[u8] {
285 let bytes = value.to_be_bytes();
286 self.build_command(&[self.address, cmd::SET_ACCELERATION, bytes[0], bytes[1]])
287 }
288
289 pub fn set_max_torque(&mut self, value: u16) -> Result<&[u8]> {
294 if value > MAX_TORQUE_LIMIT {
295 return Err(Error::InvalidValue);
296 }
297 let bytes = value.to_be_bytes();
298 Ok(self.build_command(&[self.address, cmd::SET_MAX_TORQUE, bytes[0], bytes[1]]))
299 }
300
301 pub fn read_shaft_status(&mut self) -> &[u8] {
303 self.build_command(&[self.address, cmd::READ_SHAFT_STATUS])
304 }
305
306 pub fn read_encoder_value(&mut self) -> &[u8] {
308 self.build_command(&[self.address, cmd::READ_ENCODER_VALUE])
309 }
310
311 pub fn read_pulse_count(&mut self) -> &[u8] {
313 self.build_command(&[self.address, cmd::READ_PULSE_COUNT])
314 }
315
316 pub fn read_motor_shaft_angle(&mut self) -> &[u8] {
321 self.build_command(&[self.address, cmd::READ_MOTOR_SHAFT_ANGLE])
322 }
323
324 pub fn read_en_pin_status(&mut self) -> &[u8] {
331 self.build_command(&[self.address, cmd::READ_EN_PIN_STATUS])
332 }
333
334 pub fn read_motor_shaft_angle_error(&mut self) -> &[u8] {
336 self.build_command(&[self.address, cmd::READ_MOTOR_SHAFT_ANGLE_ERROR])
337 }
338
339 pub fn read_release_status(&mut self) -> &[u8] {
341 self.build_command(&[self.address, cmd::READ_RELEASE_STATUS])
342 }
343
344 fn build_command(&mut self, cmd: &[u8]) -> &[u8] {
345 let len = cmd.len();
346 self.buffer[..len].copy_from_slice(cmd);
347 self.buffer[len] = calculate_checksum(cmd);
348 &self.buffer[..=len]
349 }
350}
351
352fn calculate_checksum(bytes: &[u8]) -> u8 {
353 bytes.iter().fold(0u8, |acc, &b| acc.wrapping_add(b))
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn test_checksum() {
362 assert_eq!(0xD7, calculate_checksum(&[0xE0, 0xF6, 0x01]));
363 }
364
365 #[test]
366 fn test_default_address() {
367 let driver = Driver::default();
368 assert_eq!(driver.address, DEFAULT_ADDRESS);
369 }
370
371 #[test]
372 fn test_with_address() {
373 let driver = Driver::with_address(0xE5);
374 assert_eq!(driver.address, 0xE5);
375
376 let driver_min = Driver::with_address(MIN_ADDRESS);
378 assert_eq!(driver_min.address, MIN_ADDRESS);
379
380 let driver_max = Driver::with_address(MAX_ADDRESS);
381 assert_eq!(driver_max.address, MAX_ADDRESS);
382 }
383
384 #[test]
385 fn test_set_current_limit_invalid_value() {
386 let mut driver = Driver::default();
387 let result = driver.set_current_limit(MAX_CURRENT_INDEX + 1);
389 assert!(matches!(result, Err(Error::InvalidValue)));
390
391 let result = driver.set_current_limit(MAX_CURRENT_INDEX);
393 assert!(result.is_ok());
394 }
395
396 #[test]
397 fn test_run_motor_invalid_speed() {
398 let mut driver = Driver::default();
399 let result = driver.run_motor(RotationDirection::Clockwise, MAX_SPEED + 1, 100);
401 assert!(matches!(result, Err(Error::InvalidValue)));
402
403 let result = driver.run_motor(RotationDirection::Clockwise, MAX_SPEED, 100);
405 assert!(result.is_ok());
406 }
407
408 #[test]
409 fn test_run_with_constant_speed_invalid_speed() {
410 let mut driver = Driver::default();
411 let result = driver.run_with_constant_speed(RotationDirection::Clockwise, MAX_SPEED + 1);
413 assert!(matches!(result, Err(Error::InvalidValue)));
414
415 let result = driver.run_with_constant_speed(RotationDirection::Clockwise, MAX_SPEED);
417 assert!(result.is_ok());
418 }
419
420 #[test]
421 fn test_calibrate_encoder() {
422 let mut driver = Driver::default();
424 let cmd = driver.calibrate_encoder();
425
426 assert_eq!(cmd.len(), 4);
428 assert_eq!(cmd[0], DEFAULT_ADDRESS);
429 assert_eq!(cmd[1], 0x80); assert_eq!(cmd[2], 0x00);
431 assert_eq!(cmd[3], 0x60);
433 }
434}