Skip to content

Commit c3b088b

Browse files
Add support for the AX series of motors.
1 parent b015eca commit c3b088b

File tree

3 files changed

+238
-0
lines changed

3 files changed

+238
-0
lines changed

src/servo/dynamixel/ax.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
//! AX robotis register (protocol v1)
2+
//!
3+
//! Despite some minor differences among AX variants, it should work for
4+
//! * AX-12
5+
//! * AX-12+
6+
//! * AX-12A
7+
//! * AX-12W
8+
//! * AX-18A
9+
//!
10+
//! See <https://emanual.robotis.com/docs/en/dxl/ax/ax-12a/> for example.
11+
12+
use crate::{generate_servo, servo::conversion::Conversion};
13+
14+
generate_servo!(
15+
AX, v1,
16+
reg: (model_number, r, 0, u16, None),
17+
reg: (firmware_version, r, 2, u8, None),
18+
reg: (id, rw, 3, u8, None),
19+
reg: (baudrate, rw, 4, u8, None),
20+
reg: (return_delay_time, rw, 5, u8, None),
21+
reg: (cw_angle_limit, rw, 6, u16, AnglePosition),
22+
reg: (ccw_angle_limit, rw, 8, u16, AnglePosition),
23+
reg: (temperature_limit, rw, 11, u8, None),
24+
reg: (min_voltage_limit, rw, 12, u8, None),
25+
reg: (max_voltage_limit, rw, 13, u8, None),
26+
reg: (max_torque, rw, 14, u16, None),
27+
reg: (status_return_level, rw, 16, u8, None),
28+
reg: (alarm_led, rw, 17, u8, None),
29+
reg: (shutdown, rw, 18, u8, None),
30+
reg: (torque_enable, rw, 24, u8, None),
31+
reg: (led, rw, 25, u8, None),
32+
reg: (cw_compliance_margin, rw, 26, u8, None),
33+
reg: (ccw_compliance_margin, rw, 27, u8, None),
34+
reg: (cw_compliance_slope, rw, 28, u8, None),
35+
reg: (ccw_compliance_slope, rw, 29, u8, None),
36+
reg: (goal_position, rw, 30, u16, AnglePosition),
37+
reg: (moving_speed, rw, 32, u16, None),
38+
reg: (torque_limit, rw, 34, u16, None),
39+
reg: (present_position, r, 36, u16, AnglePosition),
40+
reg: (present_speed, r, 38, u16, None),
41+
reg: (present_load, r, 40, u16, None),
42+
reg: (present_voltage, r, 42, u8, None),
43+
reg: (present_temperature, r, 43, u8, None),
44+
reg: (registered, r, 44, u8, None),
45+
reg: (moving, r, 46, u8, None),
46+
reg: (lock, rw, 47, u8, None),
47+
reg: (punch, rw, 48, u16, None),
48+
);
49+
50+
/// Sync read present_position, present_speed and present_load in one message
51+
///
52+
/// reg_read_only!(present_position_speed_load, 36, (i16, u16, u16))
53+
pub fn sync_read_present_position_speed_load(
54+
dph: &crate::DynamixelProtocolHandler,
55+
serial_port: &mut dyn serialport::SerialPort,
56+
ids: &[u8],
57+
) -> crate::Result<Vec<(i16, u16, u16)>> {
58+
let val = dph.sync_read(serial_port, ids, 36, 2 + 2 + 2)?;
59+
let val = val
60+
.iter()
61+
.map(|v| {
62+
(
63+
i16::from_le_bytes(v[0..2].try_into().unwrap()),
64+
u16::from_le_bytes(v[2..4].try_into().unwrap()),
65+
u16::from_le_bytes(v[4..6].try_into().unwrap()),
66+
)
67+
})
68+
.collect();
69+
70+
Ok(val)
71+
}
72+
73+
pub struct AnglePosition;
74+
const MAX_DEFLECTION: f64 = 150f64.to_radians(); // -150 to 150 deg (exclusive)
75+
impl Conversion for AnglePosition {
76+
type RegisterType = u16;
77+
type UsiType = f64;
78+
79+
fn from_raw(raw: u16) -> f64 {
80+
(2.0 * MAX_DEFLECTION * (raw as f64) / 1024.0) - MAX_DEFLECTION
81+
}
82+
83+
fn to_raw(value: f64) -> u16 {
84+
(1024.0 * (MAX_DEFLECTION + value) / (2.0 * MAX_DEFLECTION)) as u16
85+
}
86+
}
87+
88+
/// Unit conversion for AX motors
89+
pub mod conv {
90+
91+
const RPM_PER_DXL_SPEED: f64 = 0.111;
92+
const RPM_TO_RADS_FACTOR: f64 = 2.0 * std::f64::consts::PI / 60.0;
93+
94+
/// Dynamixel absolute speed to radians per second
95+
///
96+
/// Works for moving_speed in joint mode for instance
97+
pub fn dxl_abs_speed_to_rad_per_sec(speed: u16) -> f64 {
98+
let rpm = speed as f64 * RPM_PER_DXL_SPEED;
99+
rpm * RPM_TO_RADS_FACTOR
100+
}
101+
102+
/// Radians per second to dynamixel absolute speed
103+
///
104+
/// Works for moving_speed in joint mode for instance
105+
pub fn rad_per_sec_to_dxl_abs_speed(speed: f64) -> u16 {
106+
let rpm = speed / RPM_TO_RADS_FACTOR;
107+
(rpm / RPM_PER_DXL_SPEED) as u16
108+
}
109+
110+
/// Dynamixel speed to radians per second
111+
///
112+
/// Works for present_speed for instance
113+
pub fn dxl_oriented_speed_to_rad_per_sec(speed: u16) -> f64 {
114+
let cw = (speed >> 11) == 1;
115+
116+
let rad_per_sec = dxl_abs_speed_to_rad_per_sec(speed % 1024);
117+
118+
match cw {
119+
true => rad_per_sec,
120+
false => -rad_per_sec,
121+
}
122+
}
123+
124+
/// Radians per second to dynamixel speed
125+
///
126+
/// Works for present_speed for instance
127+
pub fn rad_per_sec_to_dxl_oriented_speed(speed: f64) -> u16 {
128+
let raw = rad_per_sec_to_dxl_abs_speed(speed.abs());
129+
130+
match speed < 0.0 {
131+
true => raw,
132+
false => raw + 2048,
133+
}
134+
}
135+
136+
/// Dynamixel absolute load to torque percentage
137+
///
138+
/// Works for torque_limit for instance
139+
pub fn dxl_load_to_abs_torque(load: u16) -> f64 {
140+
load as f64 / 1023.0 * 100.0
141+
}
142+
143+
/// Torque percentage to dynamixel absolute load
144+
///
145+
/// Works for torque_limit for instance
146+
pub fn torque_to_dxl_abs_load(torque: f64) -> u16 {
147+
assert!((0.0..=100.0).contains(&torque));
148+
149+
(torque * 1023.0 / 100.0) as u16
150+
}
151+
152+
/// Dynamixel load to torque percentage
153+
///
154+
/// Works for present_torque for instance
155+
pub fn dxl_load_to_oriented_torque(load: u16) -> f64 {
156+
let cw = (load >> 10) == 1;
157+
158+
let torque = dxl_load_to_abs_torque(load % 1024);
159+
160+
match cw {
161+
true => torque,
162+
false => -torque,
163+
}
164+
}
165+
166+
/// Torque percentage to dynamixel load
167+
pub fn oriented_torque_to_dxl_load(torque: f64) -> u16 {
168+
let load = torque_to_dxl_abs_load(torque.abs());
169+
170+
match torque < 0.0 {
171+
true => load,
172+
false => load + 1024,
173+
}
174+
}
175+
}
176+
177+
#[cfg(test)]
178+
mod tests {
179+
use crate::servo::{conversion::Conversion, dynamixel::ax::AnglePosition};
180+
181+
use super::conv::*;
182+
183+
#[test]
184+
fn position_conversions() {
185+
assert_eq!(AnglePosition::to_raw(0.0), 512);
186+
assert_eq!(AnglePosition::to_raw(-150f64.to_radians()), 0);
187+
assert_eq!(AnglePosition::to_raw(149.9f64.to_radians()), 1023); // 150 is invalid as per spec.
188+
assert_eq!(AnglePosition::from_raw(512), 0.0);
189+
}
190+
191+
#[test]
192+
fn abs_speed_conversions() {
193+
assert_eq!(rad_per_sec_to_dxl_abs_speed(0.0), 0);
194+
assert_eq!(rad_per_sec_to_dxl_abs_speed(0.5), 43);
195+
}
196+
197+
#[test]
198+
fn speed_conversions() {
199+
assert_eq!(dxl_oriented_speed_to_rad_per_sec(300), -3.4871678454846697);
200+
assert_eq!(
201+
dxl_oriented_speed_to_rad_per_sec(2048 + 300),
202+
3.4871678454846697
203+
);
204+
205+
// FP error if not 299, but close enough.
206+
assert_eq!(rad_per_sec_to_dxl_oriented_speed(-3.4871678454846697), 299);
207+
assert_eq!(
208+
rad_per_sec_to_dxl_oriented_speed(3.4871678454846697),
209+
2048 + 299
210+
);
211+
}
212+
213+
#[test]
214+
fn torque_conversions() {
215+
assert_eq!(torque_to_dxl_abs_load(0.0), 0);
216+
assert_eq!(torque_to_dxl_abs_load(50.0), 511);
217+
assert_eq!(torque_to_dxl_abs_load(100.0), 1023);
218+
219+
assert_eq!(dxl_load_to_abs_torque(0), 0.0);
220+
assert!((dxl_load_to_abs_torque(511) - 50.0).abs() < 1e-1);
221+
assert_eq!(dxl_load_to_abs_torque(1023), 100.0);
222+
}
223+
224+
#[test]
225+
fn load_conversions() {
226+
assert!((dxl_load_to_oriented_torque(511) + 50.0).abs() < 1e-1);
227+
assert!((dxl_load_to_oriented_torque(1024 + 512) - 50.0).abs() < 1e-1);
228+
229+
assert_eq!(oriented_torque_to_dxl_load(-50.0), 511);
230+
assert_eq!(oriented_torque_to_dxl_load(50.0), 1024 + 511);
231+
}
232+
}

src/servo/dynamixel/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod ax;
12
pub mod mx;
23
pub mod xl320;
34
pub mod xl330;

src/servo/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ pub mod orbita;
66
pub(crate) mod servo_macro;
77

88
crate::register_servo!(
9+
servo: (dynamixel, AX,
10+
(AX12, 12), // All AX12, except the W are equivalent.
11+
(AX12W, 300),
12+
(AX18A, 18)
13+
),
914
servo: (dynamixel, MX,
1015
(MX28, 29),
1116
(MX64, 310),

0 commit comments

Comments
 (0)