1use defmt::info;
9use embassy_rp::clocks::clk_sys_freq;
10use embassy_rp::pwm::{Config, Pwm};
11
12const SERVO_PERIOD_US: u16 = 20_000; pub const SERVO_MIN_US_DEFAULT: u16 = 500;
16
17pub const SERVO_MAX_US_DEFAULT: u16 = 2_500;
19
20#[macro_export]
45#[doc(hidden)]
46macro_rules! servo {
47 ($($tt:tt)*) => { $crate::__servo_impl! { $($tt)* } };
48}
49#[doc(inline)]
50pub use servo;
51
52#[doc(hidden)]
54#[macro_export]
55macro_rules! __servo_impl {
56 (@__fill_defaults
57 pin: $pin:tt,
58 slice: $slice:tt,
59 channel: $channel:tt,
60 min_us: $min_us:expr,
61 max_us: $max_us:expr,
62 max_degrees: $max_degrees:expr,
63 fields: [ ]
64 ) => {
65 $crate::__servo_impl! {
66 @__build
67 pin: $pin,
68 slice: $slice,
69 channel: $channel,
70 min_us: $min_us,
71 max_us: $max_us,
72 max_degrees: $max_degrees
73 }
74 };
75
76 (@__fill_defaults
77 pin: $pin:tt,
78 slice: $slice:tt,
79 channel: $channel:tt,
80 min_us: $min_us:expr,
81 max_us: $max_us:expr,
82 max_degrees: $max_degrees:expr,
83 fields: [ pin: $pin_value:expr, $($rest:tt)* ]
84 ) => {
85 $crate::__servo_impl! {
86 @__fill_defaults
87 pin: $pin_value,
88 slice: $slice,
89 channel: $channel,
90 min_us: $min_us,
91 max_us: $max_us,
92 max_degrees: $max_degrees,
93 fields: [ $($rest)* ]
94 }
95 };
96
97 (@__fill_defaults
98 pin: $pin:tt,
99 slice: $slice:tt,
100 channel: $channel:tt,
101 min_us: $min_us:expr,
102 max_us: $max_us:expr,
103 max_degrees: $max_degrees:expr,
104 fields: [ pin: $pin_value:expr ]
105 ) => {
106 $crate::__servo_impl! {
107 @__fill_defaults
108 pin: $pin_value,
109 slice: $slice,
110 channel: $channel,
111 min_us: $min_us,
112 max_us: $max_us,
113 max_degrees: $max_degrees,
114 fields: [ ]
115 }
116 };
117
118 (@__fill_defaults
119 pin: $pin:tt,
120 slice: $slice:tt,
121 channel: $channel:tt,
122 min_us: $min_us:expr,
123 max_us: $max_us:expr,
124 max_degrees: $max_degrees:expr,
125 fields: [ slice: $slice_value:expr, $($rest:tt)* ]
126 ) => {
127 $crate::__servo_impl! {
128 @__fill_defaults
129 pin: $pin,
130 slice: $slice_value,
131 channel: $channel,
132 min_us: $min_us,
133 max_us: $max_us,
134 max_degrees: $max_degrees,
135 fields: [ $($rest)* ]
136 }
137 };
138
139 (@__fill_defaults
140 pin: $pin:tt,
141 slice: $slice:tt,
142 channel: $channel:tt,
143 min_us: $min_us:expr,
144 max_us: $max_us:expr,
145 max_degrees: $max_degrees:expr,
146 fields: [ slice: $slice_value:expr ]
147 ) => {
148 $crate::__servo_impl! {
149 @__fill_defaults
150 pin: $pin,
151 slice: $slice_value,
152 channel: $channel,
153 min_us: $min_us,
154 max_us: $max_us,
155 max_degrees: $max_degrees,
156 fields: [ ]
157 }
158 };
159
160 (@__fill_defaults
161 pin: $pin:tt,
162 slice: $slice:tt,
163 channel: $channel:tt,
164 min_us: $min_us:expr,
165 max_us: $max_us:expr,
166 max_degrees: $max_degrees:expr,
167 fields: [ min_us: $min_us_value:expr, $($rest:tt)* ]
168 ) => {
169 $crate::__servo_impl! {
170 @__fill_defaults
171 pin: $pin,
172 slice: $slice,
173 channel: $channel,
174 min_us: $min_us_value,
175 max_us: $max_us,
176 max_degrees: $max_degrees,
177 fields: [ $($rest)* ]
178 }
179 };
180
181 (@__fill_defaults
182 pin: $pin:tt,
183 slice: $slice:tt,
184 channel: $channel:tt,
185 min_us: $min_us:expr,
186 max_us: $max_us:expr,
187 max_degrees: $max_degrees:expr,
188 fields: [ min_us: $min_us_value:expr ]
189 ) => {
190 $crate::__servo_impl! {
191 @__fill_defaults
192 pin: $pin,
193 slice: $slice,
194 channel: $channel,
195 min_us: $min_us_value,
196 max_us: $max_us,
197 max_degrees: $max_degrees,
198 fields: [ ]
199 }
200 };
201
202 (@__fill_defaults
203 pin: $pin:tt,
204 slice: $slice:tt,
205 channel: $channel:tt,
206 min_us: $min_us:expr,
207 max_us: $max_us:expr,
208 max_degrees: $max_degrees:expr,
209 fields: [ max_us: $max_us_value:expr, $($rest:tt)* ]
210 ) => {
211 $crate::__servo_impl! {
212 @__fill_defaults
213 pin: $pin,
214 slice: $slice,
215 channel: $channel,
216 min_us: $min_us,
217 max_us: $max_us_value,
218 max_degrees: $max_degrees,
219 fields: [ $($rest)* ]
220 }
221 };
222
223 (@__fill_defaults
224 pin: $pin:tt,
225 slice: $slice:tt,
226 channel: $channel:tt,
227 min_us: $min_us:expr,
228 max_us: $max_us:expr,
229 max_degrees: $max_degrees:expr,
230 fields: [ max_us: $max_us_value:expr ]
231 ) => {
232 $crate::__servo_impl! {
233 @__fill_defaults
234 pin: $pin,
235 slice: $slice,
236 channel: $channel,
237 min_us: $min_us,
238 max_us: $max_us_value,
239 max_degrees: $max_degrees,
240 fields: [ ]
241 }
242 };
243
244 (@__fill_defaults
245 pin: $pin:tt,
246 slice: $slice:tt,
247 channel: $channel:tt,
248 min_us: $min_us:expr,
249 max_us: $max_us:expr,
250 max_degrees: $max_degrees:expr,
251 fields: [ max_degrees: $max_degrees_value:expr, $($rest:tt)* ]
252 ) => {
253 $crate::__servo_impl! {
254 @__fill_defaults
255 pin: $pin,
256 slice: $slice,
257 channel: $channel,
258 min_us: $min_us,
259 max_us: $max_us,
260 max_degrees: $max_degrees_value,
261 fields: [ $($rest)* ]
262 }
263 };
264
265 (@__fill_defaults
266 pin: $pin:tt,
267 slice: $slice:tt,
268 channel: $channel:tt,
269 min_us: $min_us:expr,
270 max_us: $max_us:expr,
271 max_degrees: $max_degrees:expr,
272 fields: [ max_degrees: $max_degrees_value:expr ]
273 ) => {
274 $crate::__servo_impl! {
275 @__fill_defaults
276 pin: $pin,
277 slice: $slice,
278 channel: $channel,
279 min_us: $min_us,
280 max_us: $max_us,
281 max_degrees: $max_degrees_value,
282 fields: [ ]
283 }
284 };
285
286 (@__fill_defaults
287 pin: $pin:tt,
288 slice: $slice:tt,
289 channel: $channel:tt,
290 min_us: $min_us:expr,
291 max_us: $max_us:expr,
292 max_degrees: $max_degrees:expr,
293 fields: [ channel: A, $($rest:tt)* ]
294 ) => {
295 $crate::__servo_impl! {
296 @__fill_defaults
297 pin: $pin,
298 slice: $slice,
299 channel: A,
300 min_us: $min_us,
301 max_us: $max_us,
302 max_degrees: $max_degrees,
303 fields: [ $($rest)* ]
304 }
305 };
306
307 (@__fill_defaults
308 pin: $pin:tt,
309 slice: $slice:tt,
310 channel: $channel:tt,
311 min_us: $min_us:expr,
312 max_us: $max_us:expr,
313 max_degrees: $max_degrees:expr,
314 fields: [ channel: A ]
315 ) => {
316 $crate::__servo_impl! {
317 @__fill_defaults
318 pin: $pin,
319 slice: $slice,
320 channel: A,
321 min_us: $min_us,
322 max_us: $max_us,
323 max_degrees: $max_degrees,
324 fields: [ ]
325 }
326 };
327
328 (@__fill_defaults
329 pin: $pin:tt,
330 slice: $slice:tt,
331 channel: $channel:tt,
332 min_us: $min_us:expr,
333 max_us: $max_us:expr,
334 max_degrees: $max_degrees:expr,
335 fields: [ channel: B, $($rest:tt)* ]
336 ) => {
337 $crate::__servo_impl! {
338 @__fill_defaults
339 pin: $pin,
340 slice: $slice,
341 channel: B,
342 min_us: $min_us,
343 max_us: $max_us,
344 max_degrees: $max_degrees,
345 fields: [ $($rest)* ]
346 }
347 };
348
349 (@__fill_defaults
350 pin: $pin:tt,
351 slice: $slice:tt,
352 channel: $channel:tt,
353 min_us: $min_us:expr,
354 max_us: $max_us:expr,
355 max_degrees: $max_degrees:expr,
356 fields: [ channel: B ]
357 ) => {
358 $crate::__servo_impl! {
359 @__fill_defaults
360 pin: $pin,
361 slice: $slice,
362 channel: B,
363 min_us: $min_us,
364 max_us: $max_us,
365 max_degrees: $max_degrees,
366 fields: [ ]
367 }
368 };
369
370 (@__fill_defaults
371 pin: $pin:tt,
372 slice: $slice:tt,
373 channel: $channel:tt,
374 min_us: $min_us:expr,
375 max_us: $max_us:expr,
376 max_degrees: $max_degrees:expr,
377 fields: [ even, $($rest:tt)* ]
378 ) => {
379 $crate::__servo_impl! {
380 @__fill_defaults
381 pin: $pin,
382 slice: $slice,
383 channel: A,
384 min_us: $min_us,
385 max_us: $max_us,
386 max_degrees: $max_degrees,
387 fields: [ $($rest)* ]
388 }
389 };
390
391 (@__fill_defaults
392 pin: $pin:tt,
393 slice: $slice:tt,
394 channel: $channel:tt,
395 min_us: $min_us:expr,
396 max_us: $max_us:expr,
397 max_degrees: $max_degrees:expr,
398 fields: [ even ]
399 ) => {
400 $crate::__servo_impl! {
401 @__fill_defaults
402 pin: $pin,
403 slice: $slice,
404 channel: A,
405 min_us: $min_us,
406 max_us: $max_us,
407 max_degrees: $max_degrees,
408 fields: [ ]
409 }
410 };
411
412 (@__fill_defaults
413 pin: $pin:tt,
414 slice: $slice:tt,
415 channel: $channel:tt,
416 min_us: $min_us:expr,
417 max_us: $max_us:expr,
418 max_degrees: $max_degrees:expr,
419 fields: [ odd, $($rest:tt)* ]
420 ) => {
421 $crate::__servo_impl! {
422 @__fill_defaults
423 pin: $pin,
424 slice: $slice,
425 channel: B,
426 min_us: $min_us,
427 max_us: $max_us,
428 max_degrees: $max_degrees,
429 fields: [ $($rest)* ]
430 }
431 };
432
433 (@__fill_defaults
434 pin: $pin:tt,
435 slice: $slice:tt,
436 channel: $channel:tt,
437 min_us: $min_us:expr,
438 max_us: $max_us:expr,
439 max_degrees: $max_degrees:expr,
440 fields: [ odd ]
441 ) => {
442 $crate::__servo_impl! {
443 @__fill_defaults
444 pin: $pin,
445 slice: $slice,
446 channel: B,
447 min_us: $min_us,
448 max_us: $max_us,
449 max_degrees: $max_degrees,
450 fields: [ ]
451 }
452 };
453
454 (@__build
455 pin: _UNSET_,
456 slice: $slice:tt,
457 channel: $channel:tt,
458 min_us: $min_us:expr,
459 max_us: $max_us:expr,
460 max_degrees: $max_degrees:expr
461 ) => {
462 compile_error!("servo! requires `pin: ...`");
463 };
464
465 (@__build
466 pin: $pin:expr,
467 slice: _UNSET_,
468 channel: $channel:tt,
469 min_us: $min_us:expr,
470 max_us: $max_us:expr,
471 max_degrees: $max_degrees:expr
472 ) => {
473 compile_error!("servo! requires `slice: ...`");
474 };
475
476 (@__build
477 pin: $pin:expr,
478 slice: $slice:expr,
479 channel: _UNSET_,
480 min_us: $min_us:expr,
481 max_us: $max_us:expr,
482 max_degrees: $max_degrees:expr
483 ) => {
484 $crate::servo::servo_from_pin_slice($pin, $slice, $min_us, $max_us, $max_degrees)
485 };
486
487 (@__build
488 pin: $pin:expr,
489 slice: $slice:expr,
490 channel: A,
491 min_us: $min_us:expr,
492 max_us: $max_us:expr,
493 max_degrees: $max_degrees:expr
494 ) => {
495 $crate::servo::Servo::new_output_a(
496 embassy_rp::pwm::Pwm::new_output_a(
497 $slice,
498 $pin,
499 embassy_rp::pwm::Config::default(),
500 ),
501 $min_us,
502 $max_us,
503 $max_degrees,
504 )
505 };
506
507 (@__build
508 pin: $pin:expr,
509 slice: $slice:expr,
510 channel: B,
511 min_us: $min_us:expr,
512 max_us: $max_us:expr,
513 max_degrees: $max_degrees:expr
514 ) => {
515 $crate::servo::Servo::new_output_b(
516 embassy_rp::pwm::Pwm::new_output_b(
517 $slice,
518 $pin,
519 embassy_rp::pwm::Config::default(),
520 ),
521 $min_us,
522 $max_us,
523 $max_degrees,
524 )
525 };
526
527 (
528 $($fields:tt)*
529 ) => {
530 $crate::__servo_impl! {
531 @__fill_defaults
532 pin: _UNSET_,
533 slice: _UNSET_,
534 channel: _UNSET_,
535 min_us: $crate::servo::SERVO_MIN_US_DEFAULT,
536 max_us: $crate::servo::SERVO_MAX_US_DEFAULT,
537 max_degrees: $crate::servo::Servo::DEFAULT_MAX_DEGREES,
538 fields: [ $($fields)* ]
539 }
540 };
541}
542
543#[doc(hidden)]
545pub trait ServoPwmPin<S: embassy_rp::PeripheralType>: embassy_rp::PeripheralType {
546 const IS_CHANNEL_A: bool;
547 fn new_pwm<'d>(slice: embassy_rp::Peri<'d, S>, pin: embassy_rp::Peri<'d, Self>) -> Pwm<'d>;
548}
549
550#[doc(hidden)]
552pub fn servo_from_pin_slice<'d, P, S>(
553 pin: embassy_rp::Peri<'d, P>,
554 slice: embassy_rp::Peri<'d, S>,
555 min_us: u16,
556 max_us: u16,
557 max_degrees: u16,
558) -> Servo<'d>
559where
560 P: ServoPwmPin<S>,
561 S: embassy_rp::PeripheralType,
562{
563 let pwm = P::new_pwm(slice, pin);
564 if P::IS_CHANNEL_A {
565 Servo::new_output_a(pwm, min_us, max_us, max_degrees)
566 } else {
567 Servo::new_output_b(pwm, min_us, max_us, max_degrees)
568 }
569}
570
571macro_rules! servo_pin_map {
572 ($pin:ident, $slice:ident, A) => {
573 impl ServoPwmPin<embassy_rp::peripherals::$slice> for embassy_rp::peripherals::$pin {
574 const IS_CHANNEL_A: bool = true;
575 fn new_pwm<'d>(
576 slice: embassy_rp::Peri<'d, embassy_rp::peripherals::$slice>,
577 pin: embassy_rp::Peri<'d, Self>,
578 ) -> Pwm<'d> {
579 embassy_rp::pwm::Pwm::new_output_a(slice, pin, Config::default())
580 }
581 }
582 };
583 ($pin:ident, $slice:ident, B) => {
584 impl ServoPwmPin<embassy_rp::peripherals::$slice> for embassy_rp::peripherals::$pin {
585 const IS_CHANNEL_A: bool = false;
586 fn new_pwm<'d>(
587 slice: embassy_rp::Peri<'d, embassy_rp::peripherals::$slice>,
588 pin: embassy_rp::Peri<'d, Self>,
589 ) -> Pwm<'d> {
590 embassy_rp::pwm::Pwm::new_output_b(slice, pin, Config::default())
591 }
592 }
593 };
594}
595
596servo_pin_map!(PIN_0, PWM_SLICE0, A);
597servo_pin_map!(PIN_1, PWM_SLICE0, B);
598servo_pin_map!(PIN_2, PWM_SLICE1, A);
599servo_pin_map!(PIN_3, PWM_SLICE1, B);
600servo_pin_map!(PIN_4, PWM_SLICE2, A);
601servo_pin_map!(PIN_5, PWM_SLICE2, B);
602servo_pin_map!(PIN_6, PWM_SLICE3, A);
603servo_pin_map!(PIN_7, PWM_SLICE3, B);
604servo_pin_map!(PIN_8, PWM_SLICE4, A);
605servo_pin_map!(PIN_9, PWM_SLICE4, B);
606servo_pin_map!(PIN_10, PWM_SLICE5, A);
607servo_pin_map!(PIN_11, PWM_SLICE5, B);
608servo_pin_map!(PIN_12, PWM_SLICE6, A);
609servo_pin_map!(PIN_13, PWM_SLICE6, B);
610servo_pin_map!(PIN_14, PWM_SLICE7, A);
611servo_pin_map!(PIN_15, PWM_SLICE7, B);
612servo_pin_map!(PIN_16, PWM_SLICE0, A);
613servo_pin_map!(PIN_17, PWM_SLICE0, B);
614servo_pin_map!(PIN_18, PWM_SLICE1, A);
615servo_pin_map!(PIN_19, PWM_SLICE1, B);
616servo_pin_map!(PIN_20, PWM_SLICE2, A);
617servo_pin_map!(PIN_21, PWM_SLICE2, B);
618servo_pin_map!(PIN_22, PWM_SLICE3, A);
619servo_pin_map!(PIN_23, PWM_SLICE3, B);
620servo_pin_map!(PIN_24, PWM_SLICE4, A);
621servo_pin_map!(PIN_25, PWM_SLICE4, B);
622servo_pin_map!(PIN_26, PWM_SLICE5, A);
623servo_pin_map!(PIN_27, PWM_SLICE5, B);
624servo_pin_map!(PIN_28, PWM_SLICE6, A);
625servo_pin_map!(PIN_29, PWM_SLICE6, B);
626
627#[cfg(feature = "pico2")]
628servo_pin_map!(PIN_30, PWM_SLICE7, A);
629#[cfg(feature = "pico2")]
630servo_pin_map!(PIN_31, PWM_SLICE7, B);
631#[cfg(feature = "pico2")]
632servo_pin_map!(PIN_32, PWM_SLICE8, A);
633#[cfg(feature = "pico2")]
634servo_pin_map!(PIN_33, PWM_SLICE8, B);
635#[cfg(feature = "pico2")]
636servo_pin_map!(PIN_34, PWM_SLICE9, A);
637#[cfg(feature = "pico2")]
638servo_pin_map!(PIN_35, PWM_SLICE9, B);
639#[cfg(feature = "pico2")]
640servo_pin_map!(PIN_36, PWM_SLICE10, A);
641#[cfg(feature = "pico2")]
642servo_pin_map!(PIN_37, PWM_SLICE10, B);
643#[cfg(feature = "pico2")]
644servo_pin_map!(PIN_38, PWM_SLICE11, A);
645#[cfg(feature = "pico2")]
646servo_pin_map!(PIN_39, PWM_SLICE11, B);
647#[cfg(feature = "pico2")]
648servo_pin_map!(PIN_40, PWM_SLICE8, A);
649#[cfg(feature = "pico2")]
650servo_pin_map!(PIN_41, PWM_SLICE8, B);
651#[cfg(feature = "pico2")]
652servo_pin_map!(PIN_42, PWM_SLICE9, A);
653#[cfg(feature = "pico2")]
654servo_pin_map!(PIN_43, PWM_SLICE9, B);
655#[cfg(feature = "pico2")]
656servo_pin_map!(PIN_44, PWM_SLICE10, A);
657#[cfg(feature = "pico2")]
658servo_pin_map!(PIN_45, PWM_SLICE10, B);
659#[cfg(feature = "pico2")]
660servo_pin_map!(PIN_46, PWM_SLICE11, A);
661#[cfg(feature = "pico2")]
662servo_pin_map!(PIN_47, PWM_SLICE11, B);
663
664#[doc = include_str!("../docs/how_servos_work.md")]
671pub struct Servo<'d> {
697 pwm: Pwm<'d>,
698 cfg: Config, top: u16,
700 min_us: u16,
701 max_us: u16,
702 max_degrees: u16,
703 channel: ServoChannel, state: ServoState,
705}
706
707#[derive(Debug, Clone, Copy)]
708enum ServoChannel {
709 A,
710 B,
711}
712
713#[derive(Debug, Clone, Copy, Eq, PartialEq)]
714enum ServoState {
715 Disabled,
716 Enabled,
717}
718
719impl<'d> Servo<'d> {
720 pub const DEFAULT_MAX_DEGREES: u16 = 180;
722
723 pub(crate) fn new_output_a(pwm: Pwm<'d>, min_us: u16, max_us: u16, max_degrees: u16) -> Self {
727 Self::init(pwm, ServoChannel::A, min_us, max_us, max_degrees)
728 }
729
730 pub(crate) fn new_output_b(pwm: Pwm<'d>, min_us: u16, max_us: u16, max_degrees: u16) -> Self {
734 Self::init(pwm, ServoChannel::B, min_us, max_us, max_degrees)
735 }
736
737 fn init(
739 mut pwm: Pwm<'d>,
740 channel: ServoChannel,
741 min_us: u16,
742 max_us: u16,
743 max_degrees: u16,
744 ) -> Self {
745 assert!(min_us < max_us, "min_us must be less than max_us");
747 assert!(max_degrees > 0, "max_degrees must be positive");
748 let clk = clk_sys_freq() as u64; let mut div_int = (clk / 1_000_000).clamp(1, 255) as u16;
751 let rem = clk.saturating_sub(div_int as u64 * 1_000_000);
752 let mut div_frac = ((rem * 16 + 500_000) / 1_000_000).clamp(0, 15) as u8;
753 if div_frac == 16 {
754 div_frac = 0;
755 div_int = (div_int + 1).min(255);
756 }
757
758 let top = SERVO_PERIOD_US - 1; assert!(min_us <= top, "min_us must fit in the PWM frame");
760 assert!(max_us <= top, "max_us must fit in the PWM frame");
761
762 let mut cfg = Config::default();
763 cfg.top = top;
764 cfg.phase_correct = false; cfg.divider = (div_int as u8).into();
767
768 match channel {
770 ServoChannel::A => cfg.compare_a = 1500, ServoChannel::B => cfg.compare_b = 1500, }
773
774 cfg.enable = true; pwm.set_config(&cfg);
776
777 info!(
778 "servo clk={}Hz div={}.{} top={}",
779 clk, div_int, div_frac, top
780 );
781
782 let mut servo = Self {
783 pwm,
784 cfg, top,
786 min_us,
787 max_us,
788 max_degrees,
789 channel,
790 state: ServoState::Enabled,
791 };
792 let center_us = min_us + (max_us - min_us) / 2;
793 servo.set_pulse_us(center_us);
794 servo
795 }
796
797 pub fn set_degrees(&mut self, degrees: u16) {
803 assert!((0..=self.max_degrees).contains(°rees));
804 self.ensure_enabled();
805 let us = self.min_us as u32
806 + (u32::from(degrees)) * (u32::from(self.max_us) - u32::from(self.min_us))
807 / u32::from(self.max_degrees);
808 info!("Servo set_degrees({}) -> {}µs", degrees, us);
809 self.set_pulse_us(us as u16);
810 }
811
812 #[doc(hidden)]
817 pub fn set_pulse_us(&mut self, us: u16) {
818 assert!(us <= self.top, "pulse width must fit in the PWM frame");
819 match self.channel {
823 ServoChannel::A => self.cfg.compare_a = us,
824 ServoChannel::B => self.cfg.compare_b = us,
825 }
826 self.pwm.set_config(&self.cfg);
827 }
828
829 fn ensure_enabled(&mut self) {
830 if self.state == ServoState::Enabled {
831 return;
832 }
833
834 self.cfg.enable = true;
835 self.pwm.set_config(&self.cfg);
836 self.state = ServoState::Enabled;
837 }
838
839 pub fn relax(&mut self) {
846 if self.state == ServoState::Disabled {
847 return;
848 }
849
850 self.cfg.enable = false;
851 self.pwm.set_config(&self.cfg);
852 self.state = ServoState::Disabled;
853 }
854
855 pub fn hold(&mut self) {
859 if self.state == ServoState::Enabled {
860 return;
861 }
862
863 self.cfg.enable = true;
864 self.pwm.set_config(&self.cfg);
865 self.state = ServoState::Enabled;
866 }
867}