Skip to main content

mecha10_controllers/
l298n.rs

1//! L298N Motor Controller
2//!
3//! High-level motor controller wrapping the L298N driver for differential drive robots.
4//!
5//! # Overview
6//!
7//! This controller provides a `MotorController` implementation for L298N-based
8//! differential drive robots. It wraps the low-level `L298NDriver` and provides:
9//!
10//! - Differential drive kinematics (twist to wheel velocities)
11//! - Velocity normalization and clamping
12//! - Health monitoring and diagnostics
13//! - Dead-reckoning odometry estimation (no encoder feedback)
14//!
15//! # Example (active-low motor driver - default)
16//!
17//! ```rust,no_run
18//! use mecha10_controllers::{Controller, MotorController};
19//! use mecha10_controllers::l298n::{L298nMotorController, L298nControllerConfig, L298nMotorPins};
20//!
21//! #[tokio::main]
22//! async fn main() -> anyhow::Result<()> {
23//!     // Active-low drivers: pull pin LOW to activate direction
24//!     let config = L298nControllerConfig {
25//!         gpio_chip: "gpiochip0".to_string(),
26//!         motor_a: L298nMotorPins { in1: 19, in2: 13, enable: None, invert: false, active_low: true },
27//!         motor_b: L298nMotorPins { in1: 6, in2: 5, enable: None, invert: false, active_low: true },
28//!         pwm_frequency_hz: 1000,
29//!         wheel_base: 0.3,
30//!         max_velocity: 1.0,
31//!         max_duty_cycle: 0.8,
32//!         init_high_pins: vec![4, 11, 12, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27],
33//!     };
34//!
35//!     let mut controller = L298nMotorController::init(config).await?;
36//!     controller.start().await?;
37//!
38//!     // Set velocity (normalized -1.0 to 1.0)
39//!     controller.set_velocity(0.5, 0.5).await?; // Forward
40//!     controller.set_velocity(0.5, -0.5).await?; // Rotate
41//!
42//!     controller.stop().await?;
43//!     Ok(())
44//! }
45//! ```
46//!
47//! # Example (standard L298N with enable pins - active-high)
48//!
49//! ```rust,no_run
50//! use mecha10_controllers::{Controller, MotorController};
51//! use mecha10_controllers::l298n::{L298nMotorController, L298nControllerConfig, L298nMotorPins};
52//!
53//! #[tokio::main]
54//! async fn main() -> anyhow::Result<()> {
55//!     // Standard L298N with enable pins and active-high logic
56//!     let config = L298nControllerConfig {
57//!         gpio_chip: "gpiochip0".to_string(),
58//!         motor_a: L298nMotorPins { in1: 17, in2: 27, enable: Some(18), invert: false, active_low: false },
59//!         motor_b: L298nMotorPins { in1: 22, in2: 23, enable: Some(12), invert: false, active_low: false },
60//!         pwm_frequency_hz: 1000,
61//!         wheel_base: 0.3,
62//!         max_velocity: 1.0,
63//!         max_duty_cycle: 0.8,
64//!         init_high_pins: vec![],
65//!     };
66//!
67//!     let mut controller = L298nMotorController::init(config).await?;
68//!     controller.start().await?;
69//!     // ...
70//!     controller.stop().await?;
71//!     Ok(())
72//! }
73//! ```
74//!
75//! # Limitations
76//!
77//! - No encoder feedback - odometry is estimated from commanded velocities
78//! - No current/torque sensing - cannot detect stalls or measure actual torque
79//! - Open-loop control only - no closed-loop position or velocity control
80
81use crate::motor::*;
82use crate::{Controller, ControllerCapabilities, ControllerError, ControllerHealth, ControllerState};
83use async_trait::async_trait;
84use mecha10_core::actuator::MotorStatus;
85use mecha10_drivers_motor::l298n::{L298NConfig, L298NDriver, L298NMotorPins as DriverMotorPins};
86use mecha10_drivers_motor::{ControlMode, MotorCommand, MotorDriver};
87use serde::{Deserialize, Serialize};
88use std::collections::HashMap;
89use std::time::{SystemTime, UNIX_EPOCH};
90use tracing::{debug, info, warn};
91
92/// L298N controller configuration
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct L298nControllerConfig {
95    /// GPIO chip device (e.g., "gpiochip0")
96    pub gpio_chip: String,
97
98    /// Motor A (left) pin configuration
99    pub motor_a: L298nMotorPins,
100
101    /// Motor B (right) pin configuration
102    pub motor_b: L298nMotorPins,
103
104    /// PWM frequency in Hz
105    #[serde(default = "default_pwm_frequency")]
106    pub pwm_frequency_hz: u32,
107
108    /// Wheel base (distance between wheels) in meters
109    #[serde(default = "default_wheel_base")]
110    pub wheel_base: f32,
111
112    /// Maximum velocity (normalized, for scaling)
113    #[serde(default = "default_max_velocity")]
114    pub max_velocity: f32,
115
116    /// Maximum duty cycle (0.0-1.0) as safety limit
117    #[serde(default = "default_max_duty_cycle")]
118    pub max_duty_cycle: f32,
119
120    /// Additional GPIO pins to set HIGH on init (for motor driver boards that
121    /// need other pins in a known state). Motor pins are excluded automatically.
122    #[serde(default = "default_init_high_pins")]
123    pub init_high_pins: Vec<u32>,
124}
125
126fn default_init_high_pins() -> Vec<u32> {
127    vec![4, 11, 12, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27]
128}
129
130fn default_pwm_frequency() -> u32 {
131    1000
132}
133
134fn default_wheel_base() -> f32 {
135    0.3
136}
137
138fn default_max_velocity() -> f32 {
139    1.0
140}
141
142fn default_max_duty_cycle() -> f32 {
143    1.0
144}
145
146impl Default for L298nControllerConfig {
147    fn default() -> Self {
148        Self {
149            gpio_chip: "gpiochip0".to_string(),
150            motor_a: L298nMotorPins::default_motor_a(),
151            motor_b: L298nMotorPins::default_motor_b(),
152            pwm_frequency_hz: default_pwm_frequency(),
153            wheel_base: default_wheel_base(),
154            max_velocity: default_max_velocity(),
155            max_duty_cycle: default_max_duty_cycle(),
156            init_high_pins: default_init_high_pins(),
157        }
158    }
159}
160
161/// Pin configuration for a single motor
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct L298nMotorPins {
164    /// Direction control pin 1 (IN1 or IN3) - forward direction
165    pub in1: u32,
166
167    /// Direction control pin 2 (IN2 or IN4) - reverse direction
168    pub in2: u32,
169
170    /// Enable/PWM pin (ENA or ENB) - optional for drivers that use PWM on direction pins
171    #[serde(default)]
172    pub enable: Option<u32>,
173
174    /// Invert motor direction
175    #[serde(default)]
176    pub invert: bool,
177
178    /// Active-low logic: pull pin LOW to activate (default: false = active-high)
179    /// When true: Forward = IN1 LOW, IN2 HIGH; Stop = both HIGH
180    /// When false: Forward = IN1 HIGH, IN2 LOW; Stop = both LOW
181    #[serde(default = "default_active_low")]
182    pub active_low: bool,
183}
184
185fn default_active_low() -> bool {
186    true
187}
188
189impl L298nMotorPins {
190    /// Default pins for Motor A (left motor)
191    ///
192    /// Default configuration is for Viam Rover 2:
193    /// - Pins 19/13 for left motor, active-low, inverted
194    pub fn default_motor_a() -> Self {
195        Self {
196            in1: 19, // Forward
197            in2: 13, // Backward
198            enable: None,
199            invert: true, // Viam Rover 2 requires inversion
200            active_low: true,
201        }
202    }
203
204    /// Default pins for Motor B (right motor)
205    ///
206    /// Default configuration is for Viam Rover 2:
207    /// - Pins 6/5 for right motor, active-low, inverted
208    pub fn default_motor_b() -> Self {
209        Self {
210            in1: 6, // Forward
211            in2: 5, // Backward
212            enable: None,
213            invert: true, // Viam Rover 2 requires inversion
214            active_low: true,
215        }
216    }
217}
218
219impl From<L298nMotorPins> for DriverMotorPins {
220    fn from(pins: L298nMotorPins) -> Self {
221        DriverMotorPins {
222            in1: pins.in1,
223            in2: pins.in2,
224            enable: pins.enable,
225            invert: pins.invert,
226            active_low: pins.active_low,
227        }
228    }
229}
230
231/// L298N Motor Controller
232///
233/// Provides differential drive control for robots using L298N dual H-bridge driver.
234pub struct L298nMotorController {
235    config: L298nControllerConfig,
236    driver: L298NDriver,
237    state: ControllerState,
238    left_velocity: f32,
239    right_velocity: f32,
240    // Estimated encoder counts (dead reckoning)
241    left_encoder: i32,
242    right_encoder: i32,
243    last_update_us: u64,
244}
245
246#[async_trait]
247impl Controller for L298nMotorController {
248    type Config = L298nControllerConfig;
249    type Error = ControllerError;
250
251    async fn init(config: Self::Config) -> Result<Self, Self::Error> {
252        info!("Initializing L298N motor controller");
253        info!("Wheel base: {} m", config.wheel_base);
254        info!("Max velocity: {}", config.max_velocity);
255        info!("Max duty cycle: {}%", config.max_duty_cycle * 100.0);
256
257        // Create driver config from controller config
258        let driver_config = L298NConfig {
259            gpio_chip: config.gpio_chip.clone(),
260            motor_a: config.motor_a.clone().into(),
261            motor_b: config.motor_b.clone().into(),
262            pwm_frequency_hz: config.pwm_frequency_hz,
263            max_duty_cycle: config.max_duty_cycle,
264            update_rate_hz: 50.0,
265            init_high_pins: config.init_high_pins.clone(),
266        };
267
268        let mut driver = L298NDriver::new(driver_config);
269        driver
270            .init()
271            .await
272            .map_err(|e| ControllerError::InitializationFailed(e.to_string()))?;
273
274        Ok(Self {
275            config,
276            driver,
277            state: ControllerState::Initialized,
278            left_velocity: 0.0,
279            right_velocity: 0.0,
280            left_encoder: 0,
281            right_encoder: 0,
282            last_update_us: now_us(),
283        })
284    }
285
286    async fn start(&mut self) -> Result<(), Self::Error> {
287        info!("Starting L298N motor controller");
288
289        // Enable both motors
290        self.driver
291            .enable(0)
292            .await
293            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
294        self.driver
295            .enable(1)
296            .await
297            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
298
299        self.state = ControllerState::Running;
300        self.last_update_us = now_us();
301        info!("L298N motor controller started");
302
303        Ok(())
304    }
305
306    async fn stop(&mut self) -> Result<(), Self::Error> {
307        info!("Stopping L298N motor controller");
308
309        // Stop motors first
310        self.set_velocity(0.0, 0.0).await?;
311
312        // Disable motors
313        self.driver
314            .disable(0)
315            .await
316            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
317        self.driver
318            .disable(1)
319            .await
320            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
321
322        self.state = ControllerState::Stopped;
323        info!("L298N motor controller stopped");
324
325        Ok(())
326    }
327
328    async fn health_check(&self) -> ControllerHealth {
329        match self.state {
330            ControllerState::Running => ControllerHealth::Healthy,
331            ControllerState::Initialized => ControllerHealth::Degraded {
332                reason: "Controller initialized but not started".to_string(),
333            },
334            ControllerState::Stopped => ControllerHealth::Degraded {
335                reason: "Controller stopped".to_string(),
336            },
337            ControllerState::Error => ControllerHealth::Unhealthy {
338                reason: "Controller in error state".to_string(),
339            },
340            ControllerState::Uninitialized => ControllerHealth::Unknown,
341        }
342    }
343
344    fn capabilities(&self) -> ControllerCapabilities {
345        ControllerCapabilities::new("motor", "l298n")
346            .with_vendor("STMicroelectronics")
347            .with_model("L298N Dual H-Bridge")
348            .with_feature("velocity_control", true)
349            .with_feature("encoders", false) // No encoder feedback
350            .with_feature("position_control", false)
351            .with_feature("torque_control", false)
352            .with_feature("current_sensing", false)
353            .with_feature("temperature_sensing", false)
354            .with_metadata("wheel_base", format!("{} m", self.config.wheel_base))
355            .with_metadata("max_duty_cycle", format!("{}%", self.config.max_duty_cycle * 100.0))
356    }
357
358    async fn diagnostics(&self) -> HashMap<String, String> {
359        let mut diag = HashMap::new();
360        diag.insert("state".to_string(), format!("{:?}", self.state));
361        diag.insert("left_velocity".to_string(), format!("{:.2}", self.left_velocity));
362        diag.insert("right_velocity".to_string(), format!("{:.2}", self.right_velocity));
363        diag.insert("left_encoder_est".to_string(), self.left_encoder.to_string());
364        diag.insert("right_encoder_est".to_string(), self.right_encoder.to_string());
365        diag.insert("gpio_chip".to_string(), self.config.gpio_chip.clone());
366        diag
367    }
368}
369
370#[async_trait]
371impl MotorController for L298nMotorController {
372    async fn set_velocity(&mut self, left: f32, right: f32) -> Result<(), Self::Error> {
373        if self.state != ControllerState::Running {
374            return Err(ControllerError::InvalidState("Controller not running".to_string()));
375        }
376
377        // Clamp velocities
378        let max_vel = self.config.max_velocity;
379        let left_clamped = left.clamp(-max_vel, max_vel);
380        let right_clamped = right.clamp(-max_vel, max_vel);
381
382        // Normalize to -1.0 to 1.0 range for PWM
383        let left_norm = left_clamped / max_vel;
384        let right_norm = right_clamped / max_vel;
385
386        // Update estimated encoder counts based on velocity
387        self.update_encoder_estimates(left_norm, right_norm);
388
389        // Store velocities
390        self.left_velocity = left_clamped;
391        self.right_velocity = right_clamped;
392
393        // Send commands to driver
394        // Motor A = left (ID 0), Motor B = right (ID 1)
395        let left_cmd = MotorCommand {
396            motor_id: 0,
397            mode: ControlMode::PWM,
398            target: left_norm as f64,
399            velocity_limit: None,
400            torque_limit: None,
401            profile_velocity: None,
402            profile_acceleration: None,
403        };
404
405        let right_cmd = MotorCommand {
406            motor_id: 1,
407            mode: ControlMode::PWM,
408            target: right_norm as f64,
409            velocity_limit: None,
410            torque_limit: None,
411            profile_velocity: None,
412            profile_acceleration: None,
413        };
414
415        self.driver
416            .send_command(&left_cmd)
417            .await
418            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
419
420        self.driver
421            .send_command(&right_cmd)
422            .await
423            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
424
425        debug!(
426            "Set velocity: left={:.2}, right={:.2} (norm: {:.2}, {:.2})",
427            left_clamped, right_clamped, left_norm, right_norm
428        );
429
430        Ok(())
431    }
432
433    async fn get_encoders(&mut self) -> Result<(i32, i32), Self::Error> {
434        // L298N has no encoders - return estimated values from dead reckoning
435        warn!("L298N has no encoders - returning estimated values");
436        Ok((self.left_encoder, self.right_encoder))
437    }
438
439    async fn get_status(&mut self) -> Result<MotorStatus, Self::Error> {
440        // Convert velocity to RPM (assuming some wheel radius)
441        let wheel_radius = 0.05; // 5cm wheels assumed
442        let vel_to_rpm = 60.0 / (2.0 * std::f32::consts::PI * wheel_radius);
443
444        Ok(MotorStatus {
445            timestamp: now_us(),
446            left_motor_rpm: self.left_velocity * vel_to_rpm,
447            right_motor_rpm: self.right_velocity * vel_to_rpm,
448            battery_voltage: 0.0, // Unknown - no sensing
449            temperature_c: 0.0,   // Unknown - no sensing
450        })
451    }
452
453    async fn emergency_stop(&mut self) -> Result<(), Self::Error> {
454        info!("Emergency stop triggered!");
455
456        // Immediately stop motors
457        self.left_velocity = 0.0;
458        self.right_velocity = 0.0;
459
460        // Send stop commands
461        let stop_cmd = MotorCommand {
462            motor_id: 0,
463            mode: ControlMode::PWM,
464            target: 0.0,
465            velocity_limit: None,
466            torque_limit: None,
467            profile_velocity: None,
468            profile_acceleration: None,
469        };
470
471        // Stop both motors
472        let _ = self.driver.send_command(&stop_cmd).await;
473        let mut stop_cmd_b = stop_cmd;
474        stop_cmd_b.motor_id = 1;
475        let _ = self.driver.send_command(&stop_cmd_b).await;
476
477        // Disable motors
478        let _ = self.driver.disable(0).await;
479        let _ = self.driver.disable(1).await;
480
481        self.state = ControllerState::Stopped;
482        Ok(())
483    }
484
485    async fn enable(&mut self) -> Result<(), Self::Error> {
486        self.driver
487            .enable(0)
488            .await
489            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
490        self.driver
491            .enable(1)
492            .await
493            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
494        Ok(())
495    }
496
497    async fn disable(&mut self) -> Result<(), Self::Error> {
498        // Stop first
499        self.set_velocity(0.0, 0.0).await?;
500
501        self.driver
502            .disable(0)
503            .await
504            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
505        self.driver
506            .disable(1)
507            .await
508            .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
509        Ok(())
510    }
511
512    async fn reset_encoders(&mut self) -> Result<(), Self::Error> {
513        self.left_encoder = 0;
514        self.right_encoder = 0;
515        info!("Reset estimated encoder counts");
516        Ok(())
517    }
518}
519
520impl L298nMotorController {
521    /// Update estimated encoder counts based on velocity (dead reckoning)
522    fn update_encoder_estimates(&mut self, left_norm: f32, right_norm: f32) {
523        let now = now_us();
524        let dt_us = now.saturating_sub(self.last_update_us);
525        let dt_s = dt_us as f32 / 1_000_000.0;
526
527        // Assume 1000 encoder counts per second at full speed
528        let counts_per_second = 1000.0;
529
530        self.left_encoder += (left_norm * counts_per_second * dt_s) as i32;
531        self.right_encoder += (right_norm * counts_per_second * dt_s) as i32;
532
533        self.last_update_us = now;
534    }
535
536    /// Get current state
537    pub fn state(&self) -> ControllerState {
538        self.state
539    }
540
541    /// Get configuration
542    pub fn config(&self) -> &L298nControllerConfig {
543        &self.config
544    }
545}
546
547/// Get current time in microseconds
548fn now_us() -> u64 {
549    SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros() as u64
550}
551
552#[cfg(test)]
553mod tests {
554    use super::*;
555
556    #[tokio::test]
557    async fn test_controller_init() {
558        let config = L298nControllerConfig::default();
559        let controller = L298nMotorController::init(config).await;
560        assert!(controller.is_ok());
561    }
562
563    #[tokio::test]
564    async fn test_controller_start_stop() {
565        let config = L298nControllerConfig::default();
566        let mut controller = L298nMotorController::init(config).await.unwrap();
567
568        controller.start().await.unwrap();
569        assert_eq!(controller.state(), ControllerState::Running);
570
571        controller.stop().await.unwrap();
572        assert_eq!(controller.state(), ControllerState::Stopped);
573    }
574
575    #[tokio::test]
576    async fn test_set_velocity() {
577        let config = L298nControllerConfig::default();
578        let mut controller = L298nMotorController::init(config).await.unwrap();
579        controller.start().await.unwrap();
580
581        let result = controller.set_velocity(0.5, 0.5).await;
582        assert!(result.is_ok());
583    }
584
585    #[tokio::test]
586    async fn test_emergency_stop() {
587        let config = L298nControllerConfig::default();
588        let mut controller = L298nMotorController::init(config).await.unwrap();
589        controller.start().await.unwrap();
590
591        controller.set_velocity(1.0, 1.0).await.unwrap();
592        controller.emergency_stop().await.unwrap();
593
594        assert_eq!(controller.state(), ControllerState::Stopped);
595    }
596
597    #[tokio::test]
598    async fn test_capabilities() {
599        let config = L298nControllerConfig::default();
600        let controller = L298nMotorController::init(config).await.unwrap();
601
602        let caps = controller.capabilities();
603        assert_eq!(caps.controller_type, "motor");
604        assert_eq!(caps.implementation, "l298n");
605        assert!(!caps.supports("encoders"));
606        assert!(caps.supports("velocity_control"));
607    }
608}