Skip to content
Snippets Groups Projects
Commit 5afddae6 authored by Samuel Tardieu's avatar Samuel Tardieu
Browse files

Separate motor code into a module

parent 6a908c7d
No related branches found
No related tags found
No related merge requests found
use crate::{ use crate::{encoders::Encoders, tb6612fng::Tb6612fng};
encoders::Encoders, use core::sync::atomic::{AtomicU8, Ordering};
tb6612fng::{Movement, Tb6612fng},
};
use core::sync::atomic::{AtomicU16, AtomicU32, AtomicU8, Ordering};
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::{ use embassy_stm32::{
pac::{self, wwdg::vals::Wdgtb}, pac::{self, wwdg::vals::Wdgtb},
peripherals::WWDG, peripherals::WWDG,
time::{hz, Hertz}, time::hz,
};
use embassy_sync::{
blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, signal::Signal,
}; };
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
use embassy_time::{Duration, Instant, Ticker}; use embassy_time::{Duration, Instant, Ticker};
use futures::FutureExt; use futures::FutureExt;
use heapless::Deque; use heapless::Deque;
use i2c2_target::{I2C2, MESSAGE_SIZE}; use i2c2_target::{I2C2, MESSAGE_SIZE};
const STANDBY: u8 = 0x80; mod motor_control;
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);
static WATCHDOG_TICKS: AtomicU8 = AtomicU8::new(5); static WATCHDOG_TICKS: AtomicU8 = AtomicU8::new(5);
struct State { struct State {
...@@ -76,7 +67,7 @@ pub fn start_i2c_target( ...@@ -76,7 +67,7 @@ pub fn start_i2c_target(
STATE.as_mut().unwrap().i2c.start(&i2c_callback); STATE.as_mut().unwrap().i2c.start(&i2c_callback);
} }
spawner.spawn(watchdog()).unwrap(); 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(); static WATCHDOG_PING: Signal<CriticalSectionRawMutex, Instant> = Signal::new();
...@@ -93,81 +84,9 @@ async fn watchdog() { ...@@ -93,81 +84,9 @@ async fn watchdog() {
watchdog_expiration = ping watchdog_expiration = ping
+ Duration::from_millis(100 * u64::from(WATCHDOG_TICKS.load(Ordering::Relaxed))); + 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"); defmt::debug!("stopping motors after watchdog has expired");
standby().await; motor_control::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);
}
} }
} }
} }
...@@ -180,7 +99,7 @@ fn i2c_callback(command: &[u8], response: &mut Deque<u8, MESSAGE_SIZE>) { ...@@ -180,7 +99,7 @@ fn i2c_callback(command: &[u8], response: &mut Deque<u8, MESSAGE_SIZE>) {
} else { } else {
// If this does not succeed, this will reset due to // If this does not succeed, this will reset due to
// a lack of petting the window watchdog. // 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( ...@@ -214,9 +133,9 @@ fn process_command(
&[CMD_PWM_FREQUENCY, a, b, c] => { &[CMD_PWM_FREQUENCY, a, b, c] => {
let f = u32::from_le_bytes([a, b, c, 0]); let f = u32::from_le_bytes([a, b, c, 0]);
if (1..=100_000).contains(&f) { if (1..=100_000).contains(&f) {
if MOTOR_CONTROL if motor_control::set_pwm_frequency(hz(f))
.try_send(MotorCommand::SetFrequency(hz(f))) .now_or_never()
.is_err() .is_none()
{ {
defmt::error!("unable to queue up motor frequency"); defmt::error!("unable to queue up motor frequency");
return false; return false;
...@@ -227,14 +146,14 @@ fn process_command( ...@@ -227,14 +146,14 @@ fn process_command(
} }
} }
[CMD_PWM_FREQUENCY] => { [CMD_PWM_FREQUENCY] => {
let freq = PWM_FREQUENCY.load(Ordering::Relaxed); let freq = motor_control::get_pwm_frequency();
for &b in &freq.to_le_bytes()[..3] { for &b in &freq.0.to_le_bytes()[..3] {
response.push_back(b).unwrap(); response.push_back(b).unwrap();
} }
} }
&[CMD_MAX_MOTOR_PERCENTAGE, p] => { &[CMD_MAX_MOTOR_PERCENTAGE, p] => {
if (1..=100).contains(&p) { if (1..=100).contains(&p) {
MAX_MOTOR_PERCENTAGE.store(p, Ordering::Relaxed); motor_control::set_max_motor_percentage(p);
} else { } else {
defmt::warn!("incorrect max percentage {}", p); defmt::warn!("incorrect max percentage {}", p);
return false; return false;
...@@ -242,22 +161,22 @@ fn process_command( ...@@ -242,22 +161,22 @@ fn process_command(
} }
[CMD_MAX_MOTOR_PERCENTAGE] => { [CMD_MAX_MOTOR_PERCENTAGE] => {
response response
.push_back(MAX_MOTOR_PERCENTAGE.load(Ordering::Relaxed)) .push_back(motor_control::get_max_motor_percentage())
.unwrap(); .unwrap();
} }
&[CMD_MOTOR_SPEED, left, right] => { &[CMD_MOTOR_SPEED, left, right] => {
if MOTOR_CONTROL if motor_control::set_speed(left, right)
.try_send(MotorCommand::SetSpeed(left, right)) .now_or_never()
.is_err() .is_none()
{ {
defmt::error!("unable to queue up motor speeds"); defmt::error!("unable to queue up motor speeds");
return false; return false;
} }
} }
[CMD_MOTOR_SPEED] => { [CMD_MOTOR_SPEED] => {
let motor_speed = MOTOR_SPEED.load(Ordering::Relaxed); let (left, right) = motor_control::get_speed();
response.push_back((motor_speed >> 8) as u8).unwrap(); response.push_back(left).unwrap();
response.push_back(motor_speed as u8).unwrap(); response.push_back(right).unwrap();
} }
&[CMD_MOTOR_SHUTDOWN_TIMEOUT, ticks] => { &[CMD_MOTOR_SHUTDOWN_TIMEOUT, ticks] => {
if (1..=100).contains(&ticks) { if (1..=100).contains(&ticks) {
...@@ -281,7 +200,9 @@ fn process_command( ...@@ -281,7 +200,9 @@ fn process_command(
response.push_back(right[1]).unwrap(); response.push_back(right[1]).unwrap();
} }
[CMD_STATUS] => { [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); defmt::warn!("unknown command or args {:#04x}", command);
......
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);
}
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment