From 5afddae6e4153dda8ab05a07f6ffe4481f63530e Mon Sep 17 00:00:00 2001
From: Samuel Tardieu <sam@rfc1149.net>
Date: Wed, 17 Jul 2024 00:40:50 +0200
Subject: [PATCH] Separate motor code into a module

---
 controller/src/{logic.rs => logic/mod.rs} | 129 +++++-----------------
 controller/src/logic/motor_control.rs     | 109 ++++++++++++++++++
 2 files changed, 134 insertions(+), 104 deletions(-)
 rename controller/src/{logic.rs => logic/mod.rs} (56%)
 create mode 100644 controller/src/logic/motor_control.rs

diff --git a/controller/src/logic.rs b/controller/src/logic/mod.rs
similarity index 56%
rename from controller/src/logic.rs
rename to controller/src/logic/mod.rs
index 1a4e426..00ee9fc 100644
--- a/controller/src/logic.rs
+++ b/controller/src/logic/mod.rs
@@ -1,28 +1,19 @@
-use crate::{
-    encoders::Encoders,
-    tb6612fng::{Movement, Tb6612fng},
-};
-use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU8, Ordering};
+use crate::{encoders::Encoders, tb6612fng::Tb6612fng};
+use core::sync::atomic::{AtomicU8, Ordering};
 use embassy_executor::Spawner;
 use embassy_stm32::{
     pac::{self, wwdg::vals::Wdgtb},
     peripherals::WWDG,
-    time::{hz, Hertz},
-};
-use embassy_sync::{
-    blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal,
+    time::hz,
 };
+use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
 use embassy_time::{Duration, Instant, Ticker};
 use futures::FutureExt;
 use heapless::Deque;
 use i2c2_target::{I2C2, MESSAGE_SIZE};
 
-const STANDBY: u8 = 0x80;
-const STANDBY_BOTH: u16 = ((STANDBY as u16) << 8) | STANDBY as u16;
+mod motor_control;
 
-static MAX_MOTOR_PERCENTAGE: AtomicU8 = AtomicU8::new(100);
-static MOTOR_SPEED: AtomicU16 = AtomicU16::new(STANDBY_BOTH);
-static PWM_FREQUENCY: AtomicU32 = AtomicU32::new(0);
 static WATCHDOG_TICKS: AtomicU8 = AtomicU8::new(5);
 
 struct State {
@@ -76,7 +67,7 @@ pub fn start_i2c_target(
         STATE.as_mut().unwrap().i2c.start(&i2c_callback);
     }
     spawner.spawn(watchdog()).unwrap();
-    spawner.spawn(motor_control(motors)).unwrap();
+    spawner.spawn(motor_control::motor_control(motors)).unwrap();
 }
 
 static WATCHDOG_PING: Signal<CriticalSectionRawMutex, Instant> = Signal::new();
@@ -93,81 +84,9 @@ async fn watchdog() {
             watchdog_expiration = ping
                 + Duration::from_millis(100 * u64::from(WATCHDOG_TICKS.load(Ordering::Relaxed)));
         }
-        if is_moving() && Instant::now() >= watchdog_expiration {
+        if motor_control::is_moving() && Instant::now() >= watchdog_expiration {
             defmt::debug!("stopping motors after watchdog has expired");
-            standby().await;
-        }
-    }
-}
-
-async fn standby() {
-    MOTOR_CONTROL
-        .send(MotorCommand::SetSpeed(STANDBY, STANDBY))
-        .await;
-}
-
-fn is_moving() -> bool {
-    let motor_speed = MOTOR_SPEED.load(Ordering::Relaxed);
-    motor_speed != 0 && motor_speed != STANDBY_BOTH
-}
-
-enum MotorCommand {
-    SetSpeed(u8, u8),
-    SetFrequency(Hertz),
-}
-
-// Set channel size to 4 for:
-//  - two regular motor commands
-//  - one standby command sent by I²C
-//  - one standby command sent by the software watchdog
-static MOTOR_CONTROL: Channel<CriticalSectionRawMutex, MotorCommand, 4> = Channel::new();
-
-#[embassy_executor::task]
-#[allow(clippy::cast_possible_wrap)]
-async fn motor_control(mut motors: Tb6612fng<'static>) {
-    let (mut left, mut right, mut standby) = (0u8, 0u8, true);
-    motors.standby_enter();
-    PWM_FREQUENCY.store(motors.get_frequency().0, Ordering::Relaxed);
-    loop {
-        match MOTOR_CONTROL.receive().await {
-            MotorCommand::SetSpeed(new_left, new_right) => {
-                let (max_pwm, mmp) = (
-                    motors.max_pwm() as i32,
-                    i32::from(MAX_MOTOR_PERCENTAGE.load(Ordering::Relaxed)),
-                );
-                let scaled_speed = |speed| match speed as i8 {
-                    -128 => None,
-                    speed @ (-100..=100) => Some(i32::from(speed) * max_pwm * mmp / (100 * 100)),
-                    speed => {
-                        defmt::warn!("speed {} out of range [-100, 100]", speed);
-                        None
-                    }
-                };
-                let (speed_left, speed_right) = (scaled_speed(new_left), scaled_speed(new_right));
-                if (speed_left, speed_right) == (None, None) {
-                    motors.standby_enter();
-                    standby = true;
-                    (left, right) = (new_left, new_right);
-                } else {
-                    if standby {
-                        motors.standby_leave();
-                        standby = false;
-                    }
-                    if let Some(speed_left) = speed_left {
-                        motors.move_left(Movement::Advance(speed_left));
-                        left = new_left;
-                    }
-                    if let Some(speed_right) = speed_right {
-                        motors.move_right(Movement::Advance(speed_right));
-                        right = new_right;
-                    }
-                }
-                MOTOR_SPEED.store((u16::from(left) << 8) | u16::from(right), Ordering::Relaxed);
-            }
-            MotorCommand::SetFrequency(freq) => {
-                motors.set_frequency(freq);
-                PWM_FREQUENCY.store(freq.0, Ordering::Relaxed);
-            }
+            motor_control::standby().await;
         }
     }
 }
@@ -180,7 +99,7 @@ fn i2c_callback(command: &[u8], response: &mut Deque<u8, MESSAGE_SIZE>) {
     } else {
         // If this does not succeed, this will reset due to
         // a lack of petting the window watchdog.
-        while standby().now_or_never().is_none() {}
+        while motor_control::standby().now_or_never().is_none() {}
     }
 }
 
@@ -214,9 +133,9 @@ fn process_command(
         &[CMD_PWM_FREQUENCY, a, b, c] => {
             let f = u32::from_le_bytes([a, b, c, 0]);
             if (1..=100_000).contains(&f) {
-                if MOTOR_CONTROL
-                    .try_send(MotorCommand::SetFrequency(hz(f)))
-                    .is_err()
+                if motor_control::set_pwm_frequency(hz(f))
+                    .now_or_never()
+                    .is_none()
                 {
                     defmt::error!("unable to queue up motor frequency");
                     return false;
@@ -227,14 +146,14 @@ fn process_command(
             }
         }
         [CMD_PWM_FREQUENCY] => {
-            let freq = PWM_FREQUENCY.load(Ordering::Relaxed);
-            for &b in &freq.to_le_bytes()[..3] {
+            let freq = motor_control::get_pwm_frequency();
+            for &b in &freq.0.to_le_bytes()[..3] {
                 response.push_back(b).unwrap();
             }
         }
         &[CMD_MAX_MOTOR_PERCENTAGE, p] => {
             if (1..=100).contains(&p) {
-                MAX_MOTOR_PERCENTAGE.store(p, Ordering::Relaxed);
+                motor_control::set_max_motor_percentage(p);
             } else {
                 defmt::warn!("incorrect max percentage {}", p);
                 return false;
@@ -242,22 +161,22 @@ fn process_command(
         }
         [CMD_MAX_MOTOR_PERCENTAGE] => {
             response
-                .push_back(MAX_MOTOR_PERCENTAGE.load(Ordering::Relaxed))
+                .push_back(motor_control::get_max_motor_percentage())
                 .unwrap();
         }
         &[CMD_MOTOR_SPEED, left, right] => {
-            if MOTOR_CONTROL
-                .try_send(MotorCommand::SetSpeed(left, right))
-                .is_err()
+            if motor_control::set_speed(left, right)
+                .now_or_never()
+                .is_none()
             {
                 defmt::error!("unable to queue up motor speeds");
                 return false;
             }
         }
         [CMD_MOTOR_SPEED] => {
-            let motor_speed = MOTOR_SPEED.load(Ordering::Relaxed);
-            response.push_back((motor_speed >> 8) as u8).unwrap();
-            response.push_back(motor_speed as u8).unwrap();
+            let (left, right) = motor_control::get_speed();
+            response.push_back(left).unwrap();
+            response.push_back(right).unwrap();
         }
         &[CMD_MOTOR_SHUTDOWN_TIMEOUT, ticks] => {
             if (1..=100).contains(&ticks) {
@@ -281,7 +200,9 @@ fn process_command(
             response.push_back(right[1]).unwrap();
         }
         [CMD_STATUS] => {
-            response.push_back(u8::from(is_moving())).unwrap();
+            response
+                .push_back(u8::from(motor_control::is_moving()))
+                .unwrap();
         }
         _ => {
             defmt::warn!("unknown command or args {:#04x}", command);
diff --git a/controller/src/logic/motor_control.rs b/controller/src/logic/motor_control.rs
new file mode 100644
index 0000000..97ba144
--- /dev/null
+++ b/controller/src/logic/motor_control.rs
@@ -0,0 +1,109 @@
+use crate::tb6612fng::{Movement, Tb6612fng};
+use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU8, Ordering};
+use embassy_stm32::time::{hz, Hertz};
+use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
+
+const STANDBY: u8 = 0x80;
+const STANDBY_BOTH: u16 = ((STANDBY as u16) << 8) | STANDBY as u16;
+
+static MAX_MOTOR_PERCENTAGE: AtomicU8 = AtomicU8::new(100);
+static MOTOR_SPEED: AtomicU16 = AtomicU16::new(STANDBY_BOTH);
+static PWM_FREQUENCY: AtomicU32 = AtomicU32::new(0);
+
+pub fn set_max_motor_percentage(percent: u8) {
+    MAX_MOTOR_PERCENTAGE.store(percent, Ordering::Relaxed);
+}
+
+pub fn get_max_motor_percentage() -> u8 {
+    MAX_MOTOR_PERCENTAGE.load(Ordering::Relaxed)
+}
+
+pub async fn set_speed(left: u8, right: u8) {
+    MOTOR_CONTROL
+        .send(MotorCommand::SetSpeed(left, right))
+        .await;
+}
+
+#[allow(clippy::cast_possible_truncation)]
+pub fn get_speed() -> (u8, u8) {
+    let speed = MOTOR_SPEED.load(Ordering::Relaxed);
+    ((speed >> 8) as u8, speed as u8)
+}
+
+pub async fn standby() {
+    set_speed(STANDBY, STANDBY).await;
+}
+
+pub fn is_moving() -> bool {
+    let motor_speed = MOTOR_SPEED.load(Ordering::Relaxed);
+    motor_speed != 0 && motor_speed != STANDBY_BOTH
+}
+
+enum MotorCommand {
+    SetSpeed(u8, u8),
+    SetFrequency(Hertz),
+}
+
+pub async fn set_pwm_frequency(freq: Hertz) {
+    MOTOR_CONTROL.send(MotorCommand::SetFrequency(freq)).await;
+}
+
+pub fn get_pwm_frequency() -> Hertz {
+    hz(PWM_FREQUENCY.load(Ordering::Relaxed))
+}
+
+// Set channel size to 4 for:
+//  - two regular motor commands
+//  - one standby command sent by I²C
+//  - one standby command sent by the software watchdog
+static MOTOR_CONTROL: Channel<CriticalSectionRawMutex, MotorCommand, 4> = Channel::new();
+
+#[embassy_executor::task]
+#[allow(clippy::cast_possible_wrap)]
+pub async fn motor_control(mut motors: Tb6612fng<'static>) {
+    let (mut left, mut right, mut standby) = (0u8, 0u8, true);
+    motors.standby_enter();
+    PWM_FREQUENCY.store(motors.get_frequency().0, Ordering::Relaxed);
+    loop {
+        match MOTOR_CONTROL.receive().await {
+            MotorCommand::SetSpeed(new_left, new_right) => {
+                let (max_pwm, mmp) = (
+                    motors.max_pwm() as i32,
+                    i32::from(MAX_MOTOR_PERCENTAGE.load(Ordering::Relaxed)),
+                );
+                let scaled_speed = |speed| match speed as i8 {
+                    -128 => None,
+                    speed @ (-100..=100) => Some(i32::from(speed) * max_pwm * mmp / (100 * 100)),
+                    speed => {
+                        defmt::warn!("speed {} out of range [-100, 100]", speed);
+                        None
+                    }
+                };
+                let (speed_left, speed_right) = (scaled_speed(new_left), scaled_speed(new_right));
+                if (speed_left, speed_right) == (None, None) {
+                    motors.standby_enter();
+                    standby = true;
+                    (left, right) = (new_left, new_right);
+                } else {
+                    if standby {
+                        motors.standby_leave();
+                        standby = false;
+                    }
+                    if let Some(speed_left) = speed_left {
+                        motors.move_left(Movement::Advance(speed_left));
+                        left = new_left;
+                    }
+                    if let Some(speed_right) = speed_right {
+                        motors.move_right(Movement::Advance(speed_right));
+                        right = new_right;
+                    }
+                }
+                MOTOR_SPEED.store((u16::from(left) << 8) | u16::from(right), Ordering::Relaxed);
+            }
+            MotorCommand::SetFrequency(freq) => {
+                motors.set_frequency(freq);
+                PWM_FREQUENCY.store(freq.0, Ordering::Relaxed);
+            }
+        }
+    }
+}
-- 
GitLab