Skip to content

Commit

Permalink
[Brushed/Stepper] Refactor both control loops
Browse files Browse the repository at this point in the history
WIP
- fix E2E unit test
- make stepper control loop take 2 brushed control loop

Topic: brushed-stepper-refactor
Relative:
Reviewers:
  • Loading branch information
sahil-kale committed Oct 11, 2023
1 parent 13c78b5 commit 80c699f
Show file tree
Hide file tree
Showing 14 changed files with 719 additions and 429 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/control_loop)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/control_loop/brushed)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/control_loop/bldc)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/control_loop/stepper)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/util)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/util/pid)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/util/math)

Expand Down
216 changes: 135 additions & 81 deletions control_loop/brushed/brushed_control_loop.cpp
Original file line number Diff line number Diff line change
@@ -1,104 +1,158 @@
#include "brushed_control_loop.hpp"

#include "control_loop.hpp"
#include "math.h"
#include "math_util.hpp"
#include "util.hpp"

namespace control_loop {

BrushedControlLoop::BrushedControlLoop() {}
void BrushedControlLoop::init(BrushedControlLoopParams* params) {
params_ = params;
// Set the constants of the current controller
current_controller_.set_kp(params_->current_controller_params.kp);
current_controller_.set_ki(params_->current_controller_params.ki);
current_controller_.set_kd(params_->current_controller_params.kd);
}

void BrushedControlLoop::run(float speed) {
// Get the current time
// First check if clock is valid. If not, return early
if (!clock_) {
return;
}

utime_t current_time_us = clock_->get_time_us();

// Get the motor speed from the system manager
float motor_speed = speed;
utime_t current_time = clock_.get_time_us();
// Get the desired state
BrushedControlLoopState desired_state =
get_desired_state(speed, last_speed_, state_, current_time, deadtime_start_time_, params_->deadtime_us);

// If the desired state is different than the current state, then we need to transition to the desired state
if (desired_state != state_) {
// If the desired state is STOP, then we need to set the deadtime start time
if (desired_state == BrushedControlLoopState::STOP) {
deadtime_start_time_ = current_time;
// Reset the current controller duty cycle
current_controller_duty_cycle_ = 0.0f;
}
// If the desired state is DEADTIME_PAUSE, then we need to set the deadtime start time
else if (desired_state == BrushedControlLoopState::DEADTIME_PAUSE) {
deadtime_start_time_ = current_time;
}
// Otherwise, we need to set the deadtime start time to 0
else {
deadtime_start_time_ = 0;
}

// Get the brake mode from the params
if (params_ == nullptr) {
return;
// Set the state to the desired state
state_ = desired_state;
}
bool brake_mode = params_->brake_mode_enabled;

// Compute the motor speed outputs
h_bridge_motor_speed_outputs_t motor_speed_outputs = compute_motor_speed_outputs(motor_speed, brake_mode, current_time_us);

// Set the motor speed outputs
// TODO: implement this with 4 timers that can be registered
(void)motor_speed_outputs;
}
// Get the desired output
hwbridge::HBridge::HBridgeInput bridge_input = run_state(speed, state_);

BrushedControlLoop::h_bridge_motor_speed_outputs_t BrushedControlLoop::compute_motor_speed_outputs(float motor_speed,
bool brake_mode,
utime_t current_time_us) {
h_bridge_motor_speed_outputs_t motor_speed_outputs = {0.0f, 0.0f, 0.0f, 0.0f};
// Run the bridge
bridge_.run(bridge_input);

if (params_ == nullptr) {
return motor_speed_outputs;
}
// Update the last speed
last_speed_ = speed;
}

// Check if the motor speed is 0.0f
if (motor_speed == 0.0f) {
// Check if brake mode is enabled
if (brake_mode) {
// Set the duty cycle of the low pins to 1.0f and the high pins to 0.0f
motor_speed_outputs.DC_A_HIGH = 0.0f;
motor_speed_outputs.DC_A_LOW = 1.0f;
motor_speed_outputs.DC_B_HIGH = 0.0f;
motor_speed_outputs.DC_B_LOW = 1.0f;

} else {
// Set the duty cycle of all pins to 0.0f
motor_speed_outputs.DC_A_HIGH = 0.0f;
motor_speed_outputs.DC_A_LOW = 0.0f;
motor_speed_outputs.DC_B_HIGH = 0.0f;
motor_speed_outputs.DC_B_LOW = 0.0f;
BrushedControlLoop::BrushedControlLoopState BrushedControlLoop::get_desired_state(float desired_speed, float previous_speed,
BrushedControlLoopState current_state,
utime_t current_time,
utime_t deadtime_start_time,
utime_t deadtime_pause_time_us) {
BrushedControlLoopState desired_state = current_state;

switch (current_state) {
case BrushedControlLoopState::STOP:
if (desired_speed != 0) {
// NOTE: we should check that we are in the stop state for at least DEADTIME_PAUSE_TIME_US before transitioning to
// RUN
desired_state = BrushedControlLoopState::RUN;
}
break;
case BrushedControlLoopState::RUN: {
if (fabs(desired_speed) < math::ACCEPTABLE_FLOAT_ERROR) {
desired_state = BrushedControlLoopState::STOP;
}
// Otherwise, if the desired speed is different than the previous speed, then we need to pause the control loop for
// deadtime
else if ((desired_speed * previous_speed < 0) && (deadtime_pause_time_us > 0)) {
// If the deadtime pause ticks is > 0, then we need to pause the control loop for deadtime
desired_state = BrushedControlLoopState::DEADTIME_PAUSE;
} else {
// Do nothing, maintain the RUN state
desired_state = BrushedControlLoopState::RUN;
}
} break;
case BrushedControlLoopState::DEADTIME_PAUSE: {
// If the speed is 0, then we can transition to the STOP state
if (fabs(desired_speed) < math::ACCEPTABLE_FLOAT_ERROR) {
desired_state = BrushedControlLoopState::STOP;
}
// Otherwise, check to see if the deadtime has expired
else if ((current_time - deadtime_start_time) >= deadtime_pause_time_us) {
desired_state = BrushedControlLoopState::RUN;
} else {
// Do nothing, maintain the DEADTIME_PAUSE state
desired_state = BrushedControlLoopState::DEADTIME_PAUSE;
}
}
} else if (motor_speed >= 0.0f) {
// A high and B low should be set to a scale of absolute value of motor speed from 0.0f to 1.0f
// using the max motor speed constant
motor_speed_outputs.DC_A_HIGH = motor_speed / MAX_MOTOR_SPEED;
motor_speed_outputs.DC_A_LOW = 0.0f;
motor_speed_outputs.DC_B_HIGH = 0.0f;
motor_speed_outputs.DC_B_LOW = motor_speed / MAX_MOTOR_SPEED;
} else if (motor_speed < 0.0f) {
// A low and B high should be set to a scale of absolute value of motor speed from 0.0f to 1.0f
// using the max motor speed constant
motor_speed_outputs.DC_A_HIGH = 0.0f;
motor_speed_outputs.DC_A_LOW = -motor_speed / MAX_MOTOR_SPEED;
motor_speed_outputs.DC_B_HIGH = -motor_speed / MAX_MOTOR_SPEED;
motor_speed_outputs.DC_B_LOW = 0.0f;
default:
break;
}

// Dead time insertion- if the signs of the last successful motor speed and the current motor speed are different
// and the time is less than the last speed change time plus the dead time constant, then set the duty cycle of all
// pins to 0.0f

// First check if the signs are different
if ((motor_speed * last_motor_speed_ < 0.0f)) {
// Update the last changed time
last_speed_dir_change_time_us_ = current_time_us;
}
return desired_state;
}

// Check if the time is less than the last speed change time plus the dead time constant
if (current_time_us < params_->h_bridge_dead_time_us + last_speed_dir_change_time_us_) {
// Set the duty cycle of all pins to 0.0f
motor_speed_outputs.DC_A_HIGH = 0.0f;
motor_speed_outputs.DC_A_LOW = 0.0f;
motor_speed_outputs.DC_B_HIGH = 0.0f;
motor_speed_outputs.DC_B_LOW = 0.0f;
}
hwbridge::HBridge::HBridgeInput BrushedControlLoop::run_state(float speed, const BrushedControlLoopState& state) {
hwbridge::HBridge::HBridgeInput bridge_input = {0.0f, false, 0.0f, false};

switch (state) {
case BrushedControlLoopState::STOP: {
switch (params_->brake_mode) {
case BrushedBrakeType::COAST: {
bridge_input = {0.0f, false, 0.0f, false};
}; break;
case BrushedBrakeType::BRAKE_LOW_SIDE: {
bridge_input = {0.0f, true, 0.0f, true};
}; break;
case BrushedBrakeType::BRAKE_HIGH_SIDE: {
bridge_input = {1.0f, false, 1.0f, false};
}; break;
default:
// do nothing
break;
};
} break;
case BrushedControlLoopState::RUN: {
// If the speed is positive, then we need to drive the motor forward (A high, B low)
// If the speed is negative, then we need to drive the motor backward (A low, B high)
if (speed > 0) {
bridge_input = {speed, false, 0.0f, true};
} else {
bridge_input = {0.0f, true, -speed, false};
}
} break;
case BrushedControlLoopState::DEADTIME_PAUSE: {
// Set the bridge to float
bridge_input = {0.0f, false, 0.0f, false};
} break;
default:
// Do nothing
break;
};

return bridge_input;
}

last_motor_speed_ = motor_speed;
// Write the run_constant_current function
void BrushedControlLoop::run_constant_current(float current) {
// Get the current bridge current
float bridge_current = 0.0f;
bridge_.get_current(bridge_current);

return motor_speed_outputs;
}
// Run the current controller
current_controller_duty_cycle_ += current_controller_.calculate(current, bridge_current);

void BrushedControlLoop::init(BrushedControlLoopParams* params) {
params_ = params;
last_speed_dir_change_time_us_ = 0;
run(current_controller_duty_cycle_);
}

} // namespace control_loop
100 changes: 62 additions & 38 deletions control_loop/brushed/brushed_control_loop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,71 +2,95 @@
#define BRUSHED_CONTROL_LOOP_HPP
#include <stdint.h>

#include "bridge_hbridge.hpp"
#include "control_loop.hpp"
#include "hal_clock.hpp"
#ifdef UNIT_TEST
// forward declare the BrushedControlLoopTest class
class BrushedControlLoopTest;
#endif
#include "pid.hpp"

namespace control_loop {

// Define a class BrushedControlLoop that inherits from ControlLoop

class BrushedControlLoop : public ControlLoop {
public:
BrushedControlLoop();
BrushedControlLoop(hwbridge::HBridge& bridge, basilisk_hal::HAL_CLOCK& clock)
: bridge_(bridge), clock_(clock), current_controller_{0, 0, 0, 1.0f, -1.0f, 0} {}

class BrushedControlLoopCurrentControllerParams {
public:
float kp; // The proportional gain for the current control loop
float ki; // The integral gain for the current control loop
float kd; // The derivative gain for the current control loop
};

enum class BrushedBrakeType {
COAST,
BRAKE_LOW_SIDE,
BRAKE_HIGH_SIDE,
};

// Define a class for the brushed motor params
class BrushedControlLoopParams {
public:
bool brake_mode_enabled; // Whether brake mode is enabled or not (TODO: Make this an enum between high side or low side)
utime_t h_bridge_dead_time_us; // Amount of time to wait between switching the high and low pins of the H bridge
BrushedBrakeType brake_mode; // Whether or not to brake the motor when the control loop is not running
float deadtime_us; // The deadtime to apply to the h bridge to prevet shoot through
BrushedControlLoopCurrentControllerParams current_controller_params;
};

enum class BrushedControlLoopState {
STOP,
RUN,
DEADTIME_PAUSE,
};

/**
* @brief Initialize the control loop with the params
* @param params Pointer to the params
* @brief Initialize the control loop
* @param params The parameters for the control loop
* @return void
*/
void init(BrushedControlLoopParams* params);

void run(float speed) override;

/**
* @brief Run the control loop
* @param speed The desired speed of the motor (-1.0f->1.0f)
* @brief Run the motor at a constant current
* @param current The current to run the motor at
* @return void
*/
void run(float speed) override;
void run_constant_current(float current);

// Function to register a timer with the control loop
protected:
hwbridge::HBridge& bridge_;
basilisk_hal::HAL_CLOCK& clock_;
BrushedControlLoopParams* params_ = nullptr;

// Function to register a HAL clock with the control loop
void register_clock(basilisk_hal::HAL_CLOCK* clock) { clock_ = clock; }
BrushedControlLoopState state_ = BrushedControlLoopState::STOP;
float last_speed_ = 0.0f;
utime_t deadtime_start_time_ = 0;

// 0.0 -> 1.0f
typedef struct h_bridge_motor_speed_outputs_t {
float DC_A_HIGH;
float DC_A_LOW;
float DC_B_HIGH;
float DC_B_LOW;
} h_bridge_motor_speed_outputs_t;
// Define a PID controller for the current control loop
pid::PID<float> current_controller_;
// Define a variable to store the duty cycle commanded by the current controller
float current_controller_duty_cycle_ = 0.0f;

private:
/**
* @brief Computes the PWM duty cycle of the 4 pins based on desired motor speed (-1.0f->1.0f)
* and whether brake mode is enabled or not
*
* @param motor_speed
* @param brake_mode
* @brief Get the desired state of the control loop
* @param desired_speed The desired speed of the motor
* @param previous_speed The previous speed of the motor
* @param current_state The current state of the control loop
* @param current_time The current time
* @param deadtime_start_time The time at which the deadtime started
* @param deadtime_pause_time_us The number of ticks to pause the control loop for during deadtime
* @return The desired state of the control loop
*/
h_bridge_motor_speed_outputs_t compute_motor_speed_outputs(float motor_speed, bool brake_mode, utime_t current_time_us);
BrushedControlLoopState get_desired_state(float desired_speed, float previous_speed, BrushedControlLoopState current_state,
utime_t current_time, utime_t deadtime_start_time, utime_t deadtime_pause_time_us);

utime_t last_speed_dir_change_time_us_{0};
float last_motor_speed_{0.0f};
basilisk_hal::HAL_CLOCK* clock_{nullptr};
BrushedControlLoopParams* params_{nullptr};

#ifdef UNIT_TEST
friend class BrushedControlLoopTest;
#endif
/**
* @brief run and get the bridge input for a given speed and state
* @param speed The speed to run the motor at
* @param state The state of the control loop
*/
hwbridge::HBridge::HBridgeInput run_state(float speed, const BrushedControlLoopState& state);
};
} // namespace control_loop

Expand Down
2 changes: 1 addition & 1 deletion control_loop/control_loop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class ControlLoop {
public:
virtual void run(float speed) = 0;
// Define a max motor_speed member constant
constexpr static float MAX_MOTOR_SPEED = 1.0f;
static constexpr float MAX_MOTOR_SPEED = 1.0f;
};

} // namespace control_loop
Expand Down
Loading

0 comments on commit 80c699f

Please sign in to comment.