haptic_skin_firmware/drivers/motors.rs
1//! Pilotage direct des 8 moteurs ERM via le PWM matériel du RP2040.
2//!
3//! Chaque moteur = 1 sortie PWM (canal A d'une slice). L'intensité est
4//! exposée en 8 bits (0..=255) et mise à l'échelle vers `PWM_TOP` en interne,
5//! pour que l'API publique colle au payload du protocole série (1 octet/moteur).
6
7use embassy_rp::pwm::{Config, Pwm};
8
9use crate::config::{MOTOR_COUNT, MOTOR_MAX_DUTY, PWM_TOP};
10
11/// Possède les 8 instances PWM et expose un pilotage par intensité 0..=255.
12pub struct Motors<'d> {
13 pwms: [Pwm<'d>; MOTOR_COUNT],
14}
15
16impl<'d> Motors<'d> {
17 pub fn new(pwms: [Pwm<'d>; MOTOR_COUNT]) -> Self {
18 Self { pwms }
19 }
20
21 /// Règle l'intensité du moteur `idx` (0 = éteint, 255 = plein régime).
22 ///
23 /// Sécurité : tout chemin d'écriture (self-test, SetMotor, SetAll) est borné
24 /// ici à `MOTOR_MAX_DUTY` → impossible d'envoyer 5 V plein régime à un moteur
25 /// 3 V, quelle que soit la commande hôte.
26 pub fn set(&mut self, idx: usize, intensity: u8) {
27 let intensity = if intensity > MOTOR_MAX_DUTY {
28 MOTOR_MAX_DUTY
29 } else {
30 intensity
31 };
32 // mise à l'échelle 8 bits → résolution PWM : 255 ↦ PWM_TOP+1 (100 %)
33 let compare = ((intensity as u32 * (PWM_TOP as u32 + 1)) / 256) as u16;
34
35 // On reconstruit la config : Config::default() a `enable = false`,
36 // il faut donc réactiver le slice à chaque écriture.
37 let mut c = Config::default();
38 c.top = PWM_TOP;
39 c.enable = true;
40 c.compare_a = compare;
41 self.pwms[idx].set_config(&c);
42 }
43
44 /// Applique les 8 intensités d'un coup.
45 pub fn set_all(&mut self, intensities: &[u8; MOTOR_COUNT]) {
46 for (i, &v) in intensities.iter().enumerate() {
47 self.set(i, v);
48 }
49 }
50
51 /// Coupe tous les moteurs.
52 pub fn stop_all(&mut self) {
53 for i in 0..MOTOR_COUNT {
54 self.set(i, 0);
55 }
56 }
57}