moteus_protocol/register.rs
1// Copyright 2026 mjbots Robotic Systems, LLC. info@mjbots.com
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Register definitions for moteus controllers.
16//!
17//! The full list can be found at:
18//! <https://mjbots.github.io/moteus/protocol/registers/>
19
20use num_enum::{IntoPrimitive, TryFromPrimitive};
21
22/// Registers exposed for reading or writing from a moteus controller.
23#[non_exhaustive]
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
25#[repr(u16)]
26pub enum Register {
27 // === Telemetry Registers (0x000 - 0x00f) ===
28 /// Current mode of operation
29 Mode = 0x000,
30 /// Output shaft position in revolutions
31 Position = 0x001,
32 /// Output shaft velocity in revolutions/second
33 Velocity = 0x002,
34 /// Motor torque in Nm
35 Torque = 0x003,
36 /// Q-axis current in A
37 QCurrent = 0x004,
38 /// D-axis current in A
39 DCurrent = 0x005,
40 /// Absolute encoder position (if configured)
41 AbsPosition = 0x006,
42 /// Electrical power in W
43 Power = 0x007,
44
45 /// Motor temperature in C
46 MotorTemperature = 0x00a,
47 /// 1 if trajectory is complete
48 TrajectoryComplete = 0x00b,
49 /// Home/rezero state
50 HomeState = 0x00c,
51 /// Input voltage in V
52 Voltage = 0x00d,
53 /// Board temperature in C
54 Temperature = 0x00e,
55 /// Fault code (0 = no fault)
56 Fault = 0x00f,
57
58 // === PWM Registers (0x010 - 0x012) ===
59 /// PWM duty cycle for phase A
60 PwmPhaseA = 0x010,
61 /// PWM duty cycle for phase B
62 PwmPhaseB = 0x011,
63 /// PWM duty cycle for phase C
64 PwmPhaseC = 0x012,
65
66 // === Phase Voltage Registers (0x014 - 0x016) ===
67 /// Phase A voltage in V
68 VoltagePhaseA = 0x014,
69 /// Phase B voltage in V
70 VoltagePhaseB = 0x015,
71 /// Phase C voltage in V
72 VoltagePhaseC = 0x016,
73
74 // === VFOC Registers (0x018 - 0x01e) ===
75 /// Voltage FOC theta angle
76 VFocTheta = 0x018,
77 /// Voltage FOC voltage
78 VFocVoltage = 0x019,
79 /// Voltage DQ D component
80 VoltageDqD = 0x01a,
81 /// Voltage DQ Q component
82 VoltageDqQ = 0x01b,
83 /// Command Q current for current mode
84 CommandQCurrent = 0x01c,
85 /// Command D current for current mode
86 CommandDCurrent = 0x01d,
87 /// Voltage FOC theta rate
88 VFocThetaRate = 0x01e,
89
90 // === Position Mode Command Registers (0x020 - 0x02d) ===
91 /// Target position in revolutions
92 CommandPosition = 0x020,
93 /// Target velocity in revolutions/second
94 CommandVelocity = 0x021,
95 /// Feedforward torque in Nm
96 CommandFeedforwardTorque = 0x022,
97 /// Kp scale (0-1)
98 CommandKpScale = 0x023,
99 /// Kd scale (0-1)
100 CommandKdScale = 0x024,
101 /// Maximum torque in Nm
102 CommandPositionMaxTorque = 0x025,
103 /// Stop position (for trajectories)
104 CommandStopPosition = 0x026,
105 /// Watchdog timeout in seconds
106 CommandTimeout = 0x027,
107 /// Velocity limit in revolutions/second
108 CommandVelocityLimit = 0x028,
109 /// Acceleration limit in revolutions/second^2
110 CommandAccelLimit = 0x029,
111 /// Fixed voltage override
112 CommandFixedVoltageOverride = 0x02a,
113 /// Current limit scale
114 CommandIlimitScale = 0x02b,
115 /// Fixed current override
116 CommandFixedCurrentOverride = 0x02c,
117 /// Ignore position bounds flag
118 CommandIgnorePositionBounds = 0x02d,
119
120 // === Position Torque Reporting Registers (0x030 - 0x034) ===
121 /// Position Kp torque
122 PositionKp = 0x030,
123 /// Position Ki torque
124 PositionKi = 0x031,
125 /// Position Kd torque
126 PositionKd = 0x032,
127 /// Position feedforward torque
128 PositionFeedforward = 0x033,
129 /// Position total torque
130 PositionCommand = 0x034,
131
132 // === Control Registers (0x038 - 0x03d) ===
133 /// Control position target
134 ControlPosition = 0x038,
135 /// Control velocity target
136 ControlVelocity = 0x039,
137 /// Control torque target
138 ControlTorque = 0x03a,
139 /// Position error
140 ControlPositionError = 0x03b,
141 /// Velocity error
142 ControlVelocityError = 0x03c,
143 /// Torque error
144 ControlTorqueError = 0x03d,
145
146 // === Stay-Within Mode Command Registers (0x040 - 0x048) ===
147 /// Lower position bound
148 CommandStayWithinLowerBound = 0x040,
149 /// Upper position bound
150 CommandStayWithinUpperBound = 0x041,
151 /// Stay-within feedforward torque
152 CommandStayWithinFeedforwardTorque = 0x042,
153 /// Stay-within Kp scale
154 CommandStayWithinKpScale = 0x043,
155 /// Stay-within Kd scale
156 CommandStayWithinKdScale = 0x044,
157 /// Stay-within maximum torque
158 CommandStayWithinPositionMaxTorque = 0x045,
159 /// Stay-within timeout
160 CommandStayWithinTimeout = 0x046,
161 /// Stay-within current limit scale
162 CommandStayWithinIlimitScale = 0x047,
163 /// Stay-within ignore position bounds
164 CommandStayWithinIgnorePositionBounds = 0x048,
165
166 // === Encoder Registers (0x050 - 0x058) ===
167 /// Encoder 0 position
168 Encoder0Position = 0x050,
169 /// Encoder 0 velocity
170 Encoder0Velocity = 0x051,
171 /// Encoder 1 position
172 Encoder1Position = 0x052,
173 /// Encoder 1 velocity
174 Encoder1Velocity = 0x053,
175 /// Encoder 2 position
176 Encoder2Position = 0x054,
177 /// Encoder 2 velocity
178 Encoder2Velocity = 0x055,
179 /// Encoder validity flags
180 EncoderValidity = 0x058,
181
182 // === GPIO Registers (0x05c - 0x05f) ===
183 /// Aux1 GPIO command
184 Aux1GpioCommand = 0x05c,
185 /// Aux2 GPIO command
186 Aux2GpioCommand = 0x05d,
187 /// Aux1 GPIO status
188 Aux1GpioStatus = 0x05e,
189 /// Aux2 GPIO status
190 Aux2GpioStatus = 0x05f,
191
192 // === Analog Input Registers (0x060 - 0x06c) ===
193 /// Aux1 analog input 1
194 Aux1AnalogIn1 = 0x060,
195 /// Aux1 analog input 2
196 Aux1AnalogIn2 = 0x061,
197 /// Aux1 analog input 3
198 Aux1AnalogIn3 = 0x062,
199 /// Aux1 analog input 4
200 Aux1AnalogIn4 = 0x063,
201 /// Aux1 analog input 5
202 Aux1AnalogIn5 = 0x064,
203 /// Aux2 analog input 1
204 Aux2AnalogIn1 = 0x068,
205 /// Aux2 analog input 2
206 Aux2AnalogIn2 = 0x069,
207 /// Aux2 analog input 3
208 Aux2AnalogIn3 = 0x06a,
209 /// Aux2 analog input 4
210 Aux2AnalogIn4 = 0x06b,
211 /// Aux2 analog input 5
212 Aux2AnalogIn5 = 0x06c,
213
214 // === Timing Registers (0x070 - 0x071) ===
215 /// Millisecond counter
216 MillisecondCounter = 0x070,
217 /// Clock trim value
218 ClockTrim = 0x071,
219
220 // === PWM Input Registers (0x072 - 0x075) ===
221 /// Aux1 PWM input period in microseconds
222 Aux1PwmInputPeriod = 0x072,
223 /// Aux1 PWM input duty cycle (0-1)
224 Aux1PwmInputDutyCycle = 0x073,
225 /// Aux2 PWM input period in microseconds
226 Aux2PwmInputPeriod = 0x074,
227 /// Aux2 PWM input duty cycle (0-1)
228 Aux2PwmInputDutyCycle = 0x075,
229
230 // === PWM Output Registers (0x076 - 0x07f) ===
231 /// Aux1 PWM output 1
232 Aux1Pwm1 = 0x076,
233 /// Aux1 PWM output 2
234 Aux1Pwm2 = 0x077,
235 /// Aux1 PWM output 3
236 Aux1Pwm3 = 0x078,
237 /// Aux1 PWM output 4
238 Aux1Pwm4 = 0x079,
239 /// Aux1 PWM output 5
240 Aux1Pwm5 = 0x07a,
241 /// Aux2 PWM output 1
242 Aux2Pwm1 = 0x07b,
243 /// Aux2 PWM output 2
244 Aux2Pwm2 = 0x07c,
245 /// Aux2 PWM output 3
246 Aux2Pwm3 = 0x07d,
247 /// Aux2 PWM output 4
248 Aux2Pwm4 = 0x07e,
249 /// Aux2 PWM output 5
250 Aux2Pwm5 = 0x07f,
251
252 // === Device Info Registers (0x100 - 0x122) ===
253 /// Model number
254 ModelNumber = 0x100,
255 /// Firmware version
256 FirmwareVersion = 0x101,
257 /// Register map version
258 RegisterMapVersion = 0x102,
259 /// Multiplex CAN ID
260 MultiplexId = 0x110,
261 /// Serial number part 1
262 SerialNumber1 = 0x120,
263 /// Serial number part 2
264 SerialNumber2 = 0x121,
265 /// Serial number part 3
266 SerialNumber3 = 0x122,
267
268 // === Output Control Registers (0x130 - 0x133) ===
269 /// Set output position to nearest (alias: Rezero)
270 SetOutputNearest = 0x130,
271 /// Set output position to exact value
272 SetOutputExact = 0x131,
273 /// Require reindex before motion
274 RequireReindex = 0x132,
275 /// Recapture position and velocity
276 RecapturePositionVelocity = 0x133,
277
278 // === Driver Fault Registers (0x140 - 0x141) ===
279 /// Driver fault register 1
280 DriverFault1 = 0x140,
281 /// Driver fault register 2
282 DriverFault2 = 0x141,
283
284 // === UUID Registers (0x150 - 0x158) ===
285 /// UUID part 1
286 Uuid1 = 0x150,
287 /// UUID part 2
288 Uuid2 = 0x151,
289 /// UUID part 3
290 Uuid3 = 0x152,
291 /// UUID part 4
292 Uuid4 = 0x153,
293 /// UUID mask part 1
294 UuidMask1 = 0x154,
295 /// UUID mask part 2
296 UuidMask2 = 0x155,
297 /// UUID mask part 3
298 UuidMask3 = 0x156,
299 /// UUID mask part 4
300 UuidMask4 = 0x157,
301 /// UUID mask capability flag
302 UuidMaskCapable = 0x158,
303}
304
305impl core::fmt::Display for Register {
306 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
307 core::fmt::Debug::fmt(self, f)
308 }
309}
310
311impl Register {
312 /// Returns the register address as a u16.
313 #[inline]
314 pub const fn address(self) -> u16 {
315 self as u16
316 }
317
318 /// Creates a Register from a raw address.
319 ///
320 /// Returns `None` if the address doesn't correspond to a known register.
321 pub fn from_address(addr: u16) -> Option<Register> {
322 Self::try_from(addr).ok()
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_register_addresses() {
332 assert_eq!(Register::Mode.address(), 0x000);
333 assert_eq!(Register::Position.address(), 0x001);
334 assert_eq!(Register::Fault.address(), 0x00f);
335 assert_eq!(Register::CommandPosition.address(), 0x020);
336 }
337
338 #[test]
339 fn test_register_from_address() {
340 assert_eq!(Register::from_address(0x000), Some(Register::Mode));
341 assert_eq!(Register::from_address(0x001), Some(Register::Position));
342 assert_eq!(Register::from_address(0xfff), None);
343 }
344}