1#![doc = include_str!("../docs/how_servos_work.md")]
20use crate::Result;
109use core::cell::RefCell;
110pub use device_envoy_core::servo::Servo;
111use esp_hal::gpio::{interconnect::PeripheralOutput, DriveMode};
112use esp_hal::ledc::{channel, timer, LowSpeed};
113use esp_hal::ledc::{channel::ChannelIFace, timer::TimerIFace};
114use esp_hal::time::Rate;
115use static_cell::StaticCell;
116
117#[doc(inline)]
118pub use crate::combine;
119#[doc(inline)]
120pub use crate::servo_player::servo_player;
121#[doc(hidden)]
122pub use device_envoy_core::servo::{
123 __servo_player_animate, __servo_player_hold, __servo_player_relax, __servo_player_set_degrees,
124 device_loop,
125};
126pub use device_envoy_core::servo::{
127 combine, linear, AtEnd, ServoPlayer, ServoPlayerHandle, ServoPlayerStatic,
128};
129
130pub mod servo_player_generated {
132 #[cfg(doc)]
133 pub use crate::servo_player::servo_player_generated::*;
134}
135
136const SERVO_PERIOD_US: u32 = 20_000;
137
138pub const SERVO_MIN_US_DEFAULT: u32 = 500;
140
141pub const SERVO_MAX_US_DEFAULT: u32 = 2_500;
143
144pub struct ServoStatic {
146 timer: StaticCell<timer::Timer<'static, LowSpeed>>,
147 channel: StaticCell<channel::Channel<'static, LowSpeed>>,
148 timer_number: timer::Number,
149 channel_number: channel::Number,
150 min_us: u32,
151 max_us: u32,
152 max_degrees: u16,
153}
154
155impl ServoStatic {
156 #[must_use]
158 pub const fn new_static(
159 timer_number: timer::Number,
160 channel_number: channel::Number,
161 min_us: u32,
162 max_us: u32,
163 max_degrees: u16,
164 ) -> Self {
165 assert!(min_us < max_us, "min_us must be less than max_us");
166 assert!(max_degrees > 0, "max_degrees must be positive");
167 Self {
168 timer: StaticCell::new(),
169 channel: StaticCell::new(),
170 timer_number,
171 channel_number,
172 min_us,
173 max_us,
174 max_degrees,
175 }
176 }
177}
178
179#[doc(hidden)]
184pub struct ServoEsp {
185 channel: RefCell<&'static mut channel::Channel<'static, LowSpeed>>,
186 min_us: u32,
187 max_us: u32,
188 max_degrees: u16,
189}
190
191impl ServoEsp {
192 pub const DEFAULT_MAX_DEGREES: u16 = <Self as Servo>::DEFAULT_MAX_DEGREES;
194
195 pub fn new(
197 servo_static: &'static ServoStatic,
198 ledc: &esp_hal::ledc::Ledc<'static>,
199 pin: impl PeripheralOutput<'static>,
200 ) -> Result<Self> {
201 let timer = servo_static
202 .timer
203 .init(ledc.timer::<LowSpeed>(servo_static.timer_number));
204 timer.configure(timer::config::Config {
205 duty: timer::config::Duty::Duty14Bit,
206 clock_source: timer::LSClockSource::APBClk,
207 frequency: Rate::from_hz(50),
208 })?;
209
210 let channel = servo_static
211 .channel
212 .init(ledc.channel(servo_static.channel_number, pin));
213 channel.configure(channel::config::Config {
214 timer,
215 duty_pct: 0,
216 drive_mode: DriveMode::PushPull,
217 })?;
218
219 Ok(Self {
220 channel: RefCell::new(channel),
221 min_us: servo_static.min_us,
222 max_us: servo_static.max_us,
223 max_degrees: servo_static.max_degrees,
224 })
225 }
226
227 fn pulse_for_degrees(&self, degrees: u16) -> u32 {
228 let pulse_span = self.max_us - self.min_us;
229 self.min_us
230 + (u32::from(degrees) * pulse_span + u32::from(self.max_degrees / 2))
231 / u32::from(self.max_degrees)
232 }
233
234 fn degrees_to_duty_pct(&self, degrees: u16) -> u8 {
235 let pulse_us = self.pulse_for_degrees(degrees);
236 let duty_pct = ((pulse_us * 100) + (SERVO_PERIOD_US / 2)) / SERVO_PERIOD_US;
237 assert!(duty_pct <= u8::MAX as u32);
238 duty_pct as u8
239 }
240}
241
242impl Servo for ServoEsp {
243 const DEFAULT_MAX_DEGREES: u16 = 180;
244
245 fn set_degrees(&self, degrees: u16) {
247 assert!(degrees <= self.max_degrees);
248 let duty_pct = self.degrees_to_duty_pct(degrees);
249 self.channel
250 .borrow_mut()
251 .set_duty(duty_pct)
252 .expect("LEDC set_duty failed in Servo::set_degrees");
253 }
254
255 fn hold(&self) {}
257
258 fn relax(&self) {
260 self.channel
261 .borrow_mut()
262 .set_duty(0)
263 .expect("LEDC set_duty failed in Servo::relax");
264 }
265}
266
267#[doc(hidden)]
268pub use paste;
269
270#[macro_export]
303#[doc(hidden)]
304macro_rules! servo {
305 ($($tt:tt)*) => { $crate::__servo_impl! { $($tt)* } };
306}
307#[doc(inline)]
308pub use servo;
309
310#[doc(hidden)]
312#[macro_export]
313macro_rules! __servo_impl {
314 (
315 $name:ident {
316 $($fields:tt)*
317 }
318 ) => {
319 $crate::__servo_impl! {
320 @__parse
321 name: $name,
322 pin: [],
323 timer: [],
324 channel: [],
325 min_us: [],
326 max_us: [],
327 max_degrees: [],
328 fields: [ $($fields)* ]
329 }
330 };
331
332 (@__parse
333 name: $name:ident,
334 pin: [],
335 timer: [$($timer:ident)?],
336 channel: [$($channel:ident)?],
337 min_us: [$($min_us:expr)?],
338 max_us: [$($max_us:expr)?],
339 max_degrees: [$($max_degrees:expr)?],
340 fields: [ pin: $pin:ident $(, $($rest:tt)*)? ]
341 ) => {
342 $crate::__servo_impl! {
343 @__parse
344 name: $name,
345 pin: [$pin],
346 timer: [$($timer)?],
347 channel: [$($channel)?],
348 min_us: [$($min_us)?],
349 max_us: [$($max_us)?],
350 max_degrees: [$($max_degrees)?],
351 fields: [ $($($rest)*)? ]
352 }
353 };
354 (@__parse
355 name: $name:ident,
356 pin: [$_pin_seen:ident],
357 timer: [$($timer:ident)?],
358 channel: [$($channel:ident)?],
359 min_us: [$($min_us:expr)?],
360 max_us: [$($max_us:expr)?],
361 max_degrees: [$($max_degrees:expr)?],
362 fields: [ pin: $pin:ident $(, $($rest:tt)*)? ]
363 ) => {
364 compile_error!("servo! duplicate `pin` field");
365 };
366
367 (@__parse
368 name: $name:ident,
369 pin: [$($pin:ident)?],
370 timer: [],
371 channel: [$($channel:ident)?],
372 min_us: [$($min_us:expr)?],
373 max_us: [$($max_us:expr)?],
374 max_degrees: [$($max_degrees:expr)?],
375 fields: [ timer: $timer:ident $(, $($rest:tt)*)? ]
376 ) => {
377 $crate::__servo_impl! {
378 @__parse
379 name: $name,
380 pin: [$($pin)?],
381 timer: [$timer],
382 channel: [$($channel)?],
383 min_us: [$($min_us)?],
384 max_us: [$($max_us)?],
385 max_degrees: [$($max_degrees)?],
386 fields: [ $($($rest)*)? ]
387 }
388 };
389 (@__parse
390 name: $name:ident,
391 pin: [$($pin:ident)?],
392 timer: [$_timer_seen:ident],
393 channel: [$($channel:ident)?],
394 min_us: [$($min_us:expr)?],
395 max_us: [$($max_us:expr)?],
396 max_degrees: [$($max_degrees:expr)?],
397 fields: [ timer: $timer:ident $(, $($rest:tt)*)? ]
398 ) => {
399 compile_error!("servo! duplicate `timer` field");
400 };
401
402 (@__parse
403 name: $name:ident,
404 pin: [$($pin:ident)?],
405 timer: [$($timer:ident)?],
406 channel: [],
407 min_us: [$($min_us:expr)?],
408 max_us: [$($max_us:expr)?],
409 max_degrees: [$($max_degrees:expr)?],
410 fields: [ channel: $channel:ident $(, $($rest:tt)*)? ]
411 ) => {
412 $crate::__servo_impl! {
413 @__parse
414 name: $name,
415 pin: [$($pin)?],
416 timer: [$($timer)?],
417 channel: [$channel],
418 min_us: [$($min_us)?],
419 max_us: [$($max_us)?],
420 max_degrees: [$($max_degrees)?],
421 fields: [ $($($rest)*)? ]
422 }
423 };
424 (@__parse
425 name: $name:ident,
426 pin: [$($pin:ident)?],
427 timer: [$($timer:ident)?],
428 channel: [$_channel_seen:ident],
429 min_us: [$($min_us:expr)?],
430 max_us: [$($max_us:expr)?],
431 max_degrees: [$($max_degrees:expr)?],
432 fields: [ channel: $channel:ident $(, $($rest:tt)*)? ]
433 ) => {
434 compile_error!("servo! duplicate `channel` field");
435 };
436
437 (@__parse
438 name: $name:ident,
439 pin: [$($pin:ident)?],
440 timer: [$($timer:ident)?],
441 channel: [$($channel:ident)?],
442 min_us: [],
443 max_us: [$($max_us:expr)?],
444 max_degrees: [$($max_degrees:expr)?],
445 fields: [ min_us: $min_us:expr $(, $($rest:tt)*)? ]
446 ) => {
447 $crate::__servo_impl! {
448 @__parse
449 name: $name,
450 pin: [$($pin)?],
451 timer: [$($timer)?],
452 channel: [$($channel)?],
453 min_us: [$min_us],
454 max_us: [$($max_us)?],
455 max_degrees: [$($max_degrees)?],
456 fields: [ $($($rest)*)? ]
457 }
458 };
459 (@__parse
460 name: $name:ident,
461 pin: [$($pin:ident)?],
462 timer: [$($timer:ident)?],
463 channel: [$($channel:ident)?],
464 min_us: [$_min_us_seen:expr],
465 max_us: [$($max_us:expr)?],
466 max_degrees: [$($max_degrees:expr)?],
467 fields: [ min_us: $min_us:expr $(, $($rest:tt)*)? ]
468 ) => {
469 compile_error!("servo! duplicate `min_us` field");
470 };
471
472 (@__parse
473 name: $name:ident,
474 pin: [$($pin:ident)?],
475 timer: [$($timer:ident)?],
476 channel: [$($channel:ident)?],
477 min_us: [$($min_us:expr)?],
478 max_us: [],
479 max_degrees: [$($max_degrees:expr)?],
480 fields: [ max_us: $max_us:expr $(, $($rest:tt)*)? ]
481 ) => {
482 $crate::__servo_impl! {
483 @__parse
484 name: $name,
485 pin: [$($pin)?],
486 timer: [$($timer)?],
487 channel: [$($channel)?],
488 min_us: [$($min_us)?],
489 max_us: [$max_us],
490 max_degrees: [$($max_degrees)?],
491 fields: [ $($($rest)*)? ]
492 }
493 };
494 (@__parse
495 name: $name:ident,
496 pin: [$($pin:ident)?],
497 timer: [$($timer:ident)?],
498 channel: [$($channel:ident)?],
499 min_us: [$($min_us:expr)?],
500 max_us: [$_max_us_seen:expr],
501 max_degrees: [$($max_degrees:expr)?],
502 fields: [ max_us: $max_us:expr $(, $($rest:tt)*)? ]
503 ) => {
504 compile_error!("servo! duplicate `max_us` field");
505 };
506
507 (@__parse
508 name: $name:ident,
509 pin: [$($pin:ident)?],
510 timer: [$($timer:ident)?],
511 channel: [$($channel:ident)?],
512 min_us: [$($min_us:expr)?],
513 max_us: [$($max_us:expr)?],
514 max_degrees: [],
515 fields: [ max_degrees: $max_degrees:expr $(, $($rest:tt)*)? ]
516 ) => {
517 $crate::__servo_impl! {
518 @__parse
519 name: $name,
520 pin: [$($pin)?],
521 timer: [$($timer)?],
522 channel: [$($channel)?],
523 min_us: [$($min_us)?],
524 max_us: [$($max_us)?],
525 max_degrees: [$max_degrees],
526 fields: [ $($($rest)*)? ]
527 }
528 };
529 (@__parse
530 name: $name:ident,
531 pin: [$($pin:ident)?],
532 timer: [$($timer:ident)?],
533 channel: [$($channel:ident)?],
534 min_us: [$($min_us:expr)?],
535 max_us: [$($max_us:expr)?],
536 max_degrees: [$_max_degrees_seen:expr],
537 fields: [ max_degrees: $max_degrees:expr $(, $($rest:tt)*)? ]
538 ) => {
539 compile_error!("servo! duplicate `max_degrees` field");
540 };
541
542 (@__parse
543 name: $name:ident,
544 pin: [$($pin:ident)?],
545 timer: [$($timer:ident)?],
546 channel: [$($channel:ident)?],
547 min_us: [$($min_us:expr)?],
548 max_us: [$($max_us:expr)?],
549 max_degrees: [$($max_degrees:expr)?],
550 fields: [ ]
551 ) => {
552 $crate::__servo_impl! {
553 @__finish
554 name: $name,
555 pin: [$($pin)?],
556 timer: [$($timer)?],
557 channel: [$($channel)?],
558 min_us: [$($min_us)?],
559 max_us: [$($max_us)?],
560 max_degrees: [$($max_degrees)?]
561 }
562 };
563
564 (@__parse
565 name: $name:ident,
566 pin: [$($pin:ident)?],
567 timer: [$($timer:ident)?],
568 channel: [$($channel:ident)?],
569 min_us: [$($min_us:expr)?],
570 max_us: [$($max_us:expr)?],
571 max_degrees: [$($max_degrees:expr)?],
572 fields: [ $field:ident : $($value:tt)+ ]
573 ) => {
574 compile_error!(
575 "servo! unknown field; expected `pin`, `timer`, `channel`, `min_us`, `max_us`, or `max_degrees`"
576 );
577 };
578
579 (@__finish
580 name: $name:ident,
581 pin: [],
582 timer: [$($timer:ident)?],
583 channel: [$($channel:ident)?],
584 min_us: [$($min_us:expr)?],
585 max_us: [$($max_us:expr)?],
586 max_degrees: [$($max_degrees:expr)?]
587 ) => {
588 compile_error!("servo! missing required `pin` field");
589 };
590 (@__finish
591 name: $name:ident,
592 pin: [$pin:ident],
593 timer: [],
594 channel: [$($channel:ident)?],
595 min_us: [$($min_us:expr)?],
596 max_us: [$($max_us:expr)?],
597 max_degrees: [$($max_degrees:expr)?]
598 ) => {
599 compile_error!("servo! missing required `timer` field");
600 };
601 (@__finish
602 name: $name:ident,
603 pin: [$pin:ident],
604 timer: [$timer:ident],
605 channel: [],
606 min_us: [$($min_us:expr)?],
607 max_us: [$($max_us:expr)?],
608 max_degrees: [$($max_degrees:expr)?]
609 ) => {
610 compile_error!("servo! missing required `channel` field");
611 };
612 (@__finish
613 name: $name:ident,
614 pin: [$pin:ident],
615 timer: [$timer:ident],
616 channel: [$channel:ident],
617 min_us: [$($min_us:expr)?],
618 max_us: [$($max_us:expr)?],
619 max_degrees: [$($max_degrees:expr)?]
620 ) => {
621 $crate::servo::paste::paste! {
622 pub struct $name;
623
624 #[used]
627 #[unsafe(no_mangle)]
628 static [<__device_envoy_esp_ledc_timer_claim_ $timer:lower>]: u8 = 0;
629
630 #[used]
631 #[unsafe(no_mangle)]
632 static [<__device_envoy_esp_ledc_channel_claim_ $channel:lower>]: u8 = 0;
633
634 static [<$name:upper _SERVO_STATIC>]: [<$name Static>] = $name::new_static();
635
636 pub struct [<$name Static>] {
637 servo_static: $crate::servo::ServoStatic,
638 }
639
640 impl $name {
641 #[must_use]
642 pub const fn new_static() -> [<$name Static>] {
643 [<$name Static>] {
644 servo_static: $crate::servo::ServoStatic::new_static(
645 ::esp_hal::ledc::timer::Number::$timer,
646 ::esp_hal::ledc::channel::Number::$channel,
647 $crate::__servo_impl!(@min_us $($min_us)?),
648 $crate::__servo_impl!(@max_us $($max_us)?),
649 $crate::__servo_impl!(@max_degrees $($max_degrees)?),
650 ),
651 }
652 }
653
654 pub fn new(
655 ledc: &::esp_hal::ledc::Ledc<'static>,
656 pin: ::esp_hal::peripherals::$pin<'static>,
657 ) -> $crate::Result<$crate::servo::ServoEsp> {
658 $crate::servo::ServoEsp::new(&[<$name:upper _SERVO_STATIC>].servo_static, ledc, pin)
659 }
660 }
661 }
662 };
663
664 (@min_us $min_us:expr) => { $min_us };
665 (@min_us) => { $crate::servo::SERVO_MIN_US_DEFAULT };
666 (@max_us $max_us:expr) => { $max_us };
667 (@max_us) => { $crate::servo::SERVO_MAX_US_DEFAULT };
668 (@max_degrees $max_degrees:expr) => { $max_degrees };
669 (@max_degrees) => { $crate::servo::ServoEsp::DEFAULT_MAX_DEGREES };
670}