Updated the drivetrain control loop for this year.
Still need a couple unit tests, but the current test passes.
diff --git a/frc971/control_loops/DriveTrain.mat b/frc971/control_loops/DriveTrain.mat
deleted file mode 100644
index fcc6f45..0000000
--- a/frc971/control_loops/DriveTrain.mat
+++ /dev/null
@@ -1,12 +0,0 @@
-#include "frc971/control_loops/StateFeedbackLoop.h"
-
-typedef StateFeedbackLoop<4, 2> MatrixClass;
-#define MATRIX_INIT A << 1.0000000000, 0.0095410093, 0.0000000000, -0.0000167223, 0.0000000000, 0.9096302600, 0.0000000000, -0.0032396985, 0.0000000000, -0.0000167223, 1.0000000000, 0.0095410093, 0.0000000000, -0.0032396985, 0.0000000000, 0.9096302600; \
-B << 0.0000628338, 0.0000022892, 0.0123712263, 0.0004435007, 0.0000022892, 0.0000628338, 0.0004435007, 0.0123712263; \
-C << 1.0000000000, 0.0000000000, 0.0000000000, 0.0000000000, 0.0000000000, 0.0000000000, 1.0000000000, 0.0000000000; \
-D << 0.0000000000, 0.0000000000, 0.0000000000, 0.0000000000; \
-L << 1.7496302600, -0.0032396985, 72.1296388532, -0.4369906587, -0.0032396985, 1.7496302600, -0.4369906586, 72.1296388532; \
-K << 242.8102455120, 19.7898401032, -8.7045950610, -0.9720464423, -8.7045950610, -0.9720464423, 242.8102455120, 19.7898401032; \
-U_max << 12.0000000000, 12.0000000000; \
-U_min << -12.0000000000, -12.0000000000; \
-
diff --git a/frc971/control_loops/StateFeedbackLoop.h b/frc971/control_loops/StateFeedbackLoop.h
deleted file mode 100644
index ad37f49..0000000
--- a/frc971/control_loops/StateFeedbackLoop.h
+++ /dev/null
@@ -1,131 +0,0 @@
-#ifndef FRC971_CONTROL_LOOPS_STATEFEEDBACKLOOP_H_
-#define FRC971_CONTROL_LOOPS_STATEFEEDBACKLOOP_H_
-
-// wikipedia article is <http://en.wikipedia.org/wiki/State_observer>
-
-#include "Eigen/Dense"
-
-template <int number_of_states, int number_of_outputs>
-class StateFeedbackPlant {
- public:
- Eigen::Matrix<double, number_of_states, 1> X;
- Eigen::Matrix<double, number_of_outputs, 1> Y;
- Eigen::Matrix<double, number_of_outputs, 1> U;
- Eigen::Matrix<double, number_of_outputs, 1> U_max;
- Eigen::Matrix<double, number_of_outputs, 1> U_min;
- Eigen::Matrix<double, number_of_states, number_of_states> A;
- Eigen::Matrix<double, number_of_states, number_of_outputs> B;
- Eigen::Matrix<double, number_of_outputs, number_of_states> C;
- Eigen::Matrix<double, number_of_outputs, number_of_outputs> D;
- // TODO(aschuh): These following 2 lines are here because MATRIX_INIT
- // assumes that you have a controller as well as a plant.
- Eigen::Matrix<double, number_of_states, number_of_outputs> L;
- Eigen::Matrix<double, number_of_outputs, number_of_states> K;
-
- StateFeedbackPlant() {
- X.setZero();
- Y.setZero();
- U.setZero();
- }
-
- virtual ~StateFeedbackPlant() {}
-
- // If U is outside the hardware range, limit it before the plant tries to use
- // it.
- virtual void CapU() {
- for (int i = 0; i < number_of_outputs; ++i) {
- if (U[i] > U_max[i]) {
- U[i] = U_max[i];
- } else if (U[i] < U_min[i]) {
- U[i] = U_min[i];
- }
- }
- }
- // Computes the new X and Y given the control input.
- void Update() {
- CapU();
- X = A * X + B * U;
- Y = C * X + D * U;
- }
-
- protected:
- // these are accessible from non-templated subclasses
- static const int number_of_states_var = number_of_states;
- static const int number_of_outputs_var = number_of_outputs;
-};
-
-template <int number_of_states, int number_of_outputs>
-class StateFeedbackLoop {
- public:
- Eigen::Matrix<double, number_of_states, 1> X;
- Eigen::Matrix<double, number_of_states, 1> X_hat;
- Eigen::Matrix<double, number_of_outputs, 1> Y;
- Eigen::Matrix<double, number_of_states, 1> R;
- Eigen::Matrix<double, number_of_outputs, 1> U;
- Eigen::Matrix<double, number_of_outputs, 1> U_max;
- Eigen::Matrix<double, number_of_outputs, 1> U_min;
- Eigen::Matrix<double, number_of_outputs, 1> U_ff;
- Eigen::Matrix<double, number_of_states, number_of_states> A;
- Eigen::Matrix<double, number_of_states, number_of_outputs> B;
- // K in wikipedia article
- Eigen::Matrix<double, number_of_outputs, number_of_states> C;
- Eigen::Matrix<double, number_of_outputs, number_of_outputs> D;
- // B in wikipedia article
- Eigen::Matrix<double, number_of_states, number_of_outputs> L;
- // C in wikipedia article
- Eigen::Matrix<double, number_of_outputs, number_of_states> K;
-
- StateFeedbackLoop() {
- // You have to initialize all the matrices to 0 or else all their elements
- // are undefined.
- X.setZero();
- X_hat.setZero();
- Y.setZero();
- R.setZero();
- U.setZero();
- U_ff.setZero();
- }
- virtual ~StateFeedbackLoop() {}
-
- virtual void FeedForward() {
- for (int i = 0; i < number_of_outputs; ++i) {
- U_ff[i] = 0.0;
- }
- }
- // If U is outside the hardware range, limit it before it
- // gets added to the observer.
- virtual void CapU() {
- for (int i = 0; i < number_of_outputs; ++i) {
- if (U[i] > U_max[i]) {
- U[i] = U_max[i];
- } else if (U[i] < U_min[i]) {
- U[i] = U_min[i];
- }
- }
- }
- // update_observer is whether or not to use the values in Y.
- // stop_motors is whether or not to output all 0s.
- void Update(bool update_observer, bool stop_motors) {
- if (stop_motors) {
- for (int i = 0; i < number_of_outputs; ++i) {
- U[i] = 0.0;
- }
- } else {
- // new power = constant * (goal - current prediction)
- U.noalias() = K * (R - X_hat);
- CapU();
- }
-
- if (update_observer) {
- X_hat = (A - L * C) * X_hat + L * Y + B * U;
- } else {
- X_hat = A * X_hat + B * U;
- }
- }
-
- protected:
- // these are accessible from non-templated subclasses
- static const int number_of_states_var = number_of_states;
- static const int number_of_outputs_var = number_of_outputs;
-};
-#endif // FRC971_CONTROL_LOOPS_STATEFEEDBACKLOOP_H_
diff --git a/frc971/control_loops/control_loops.gyp b/frc971/control_loops/control_loops.gyp
index 18df792..ea09d11 100644
--- a/frc971/control_loops/control_loops.gyp
+++ b/frc971/control_loops/control_loops.gyp
@@ -1,9 +1,4 @@
{
- 'variables': {
- 'loop_files': [
- 'DriveTrain.q',
- ]
- },
'targets': [
{
'target_name': 'state_feedback_loop',
@@ -19,37 +14,5 @@
'<(EXTERNALS):eigen',
],
},
- {
- 'target_name': 'control_loops',
- 'type': 'static_library',
- 'sources': ['<@(loop_files)'],
- 'variables': {
- 'header_path': 'frc971/control_loops',
- },
- 'dependencies': [
- '<(AOS)/common/common.gyp:control_loop_queues',
- '<(AOS)/common/common.gyp:queues',
- ],
- 'export_dependent_settings': [
- '<(AOS)/common/common.gyp:control_loop_queues',
- '<(AOS)/common/common.gyp:queues',
- ],
- 'includes': ['../../aos/build/queues.gypi'],
- },
- {
- 'target_name': 'DriveTrain',
- 'type': 'executable',
- 'sources': [
- 'DriveTrain.cc',
- ],
- 'dependencies': [
- '<(AOS)/build/aos.gyp:logging',
- '<(AOS)/common/common.gyp:controls',
- 'control_loops',
- '<(DEPTH)/frc971/queues/queues.gyp:queues',
- '<(AOS)/atom_code/atom_code.gyp:init',
- 'state_feedback_loop',
- ],
- },
],
}
diff --git a/frc971/control_loops/DriveTrain.cc b/frc971/control_loops/drivetrain/drivetrain.cc
similarity index 95%
rename from frc971/control_loops/DriveTrain.cc
rename to frc971/control_loops/drivetrain/drivetrain.cc
index bcae82d..af04086 100644
--- a/frc971/control_loops/DriveTrain.cc
+++ b/frc971/control_loops/drivetrain/drivetrain.cc
@@ -1,4 +1,4 @@
-#include "frc971/control_loops/DriveTrain.h"
+#include "frc971/control_loops/drivetrain/drivetrain.h"
#include <stdio.h>
#include <sched.h>
@@ -7,8 +7,9 @@
#include "aos/aos_core.h"
#include "aos/common/logging/logging.h"
#include "aos/common/queue.h"
-#include "frc971/control_loops/DriveTrain.mat"
-#include "frc971/control_loops/DriveTrain.q.h"
+#include "frc971/control_loops/state_feedback_loop.h"
+#include "frc971/control_loops/drivetrain/drivetrain_motor_plant.h"
+#include "frc971/control_loops/drivetrain/drivetrain.q.h"
#include "frc971/queues/GyroAngle.q.h"
#include "frc971/queues/Piston.q.h"
@@ -20,10 +21,10 @@
// Width of the robot.
const double width = 22.0 / 100.0 * 2.54;
-class DrivetrainMotorsSS : public MatrixClass {
+class DrivetrainMotorsSS : public StateFeedbackLoop<4, 2, 2> {
public:
- DrivetrainMotorsSS (void) {
- MATRIX_INIT;
+ DrivetrainMotorsSS (void)
+ : StateFeedbackLoop(MakeDrivetrainLoop()) {
_offset = 0;
_integral_offset = 0;
_left_goal = 0.0;
@@ -298,5 +299,3 @@
} // namespace control_loops
} // namespace frc971
-
-AOS_RUN_LOOP(frc971::control_loops::DrivetrainLoop)
diff --git a/frc971/control_loops/drivetrain/drivetrain.gyp b/frc971/control_loops/drivetrain/drivetrain.gyp
new file mode 100644
index 0000000..84c4a3a
--- /dev/null
+++ b/frc971/control_loops/drivetrain/drivetrain.gyp
@@ -0,0 +1,70 @@
+{
+ 'targets': [
+ {
+ 'target_name': 'drivetrain_loop',
+ 'type': 'static_library',
+ 'sources': ['drivetrain.q'],
+ 'variables': {
+ 'header_path': 'frc971/control_loops/drivetrain',
+ },
+ 'dependencies': [
+ '<(AOS)/build/aos.gyp:libaos',
+ '<(AOS)/common/common.gyp:control_loop_queues',
+ '<(AOS)/common/common.gyp:queues',
+ ],
+ 'export_dependent_settings': [
+ '<(AOS)/build/aos.gyp:libaos',
+ '<(AOS)/common/common.gyp:control_loop_queues',
+ '<(AOS)/common/common.gyp:queues',
+ ],
+ 'includes': ['../../../aos/build/queues.gypi'],
+ },
+ {
+ 'target_name': 'drivetrain_lib',
+ 'type': 'static_library',
+ 'sources': [
+ 'drivetrain.cc',
+ 'drivetrain_motor_plant.cc',
+ ],
+ 'dependencies': [
+ '<(AOS)/build/aos.gyp:libaos',
+ 'drivetrain_loop',
+ '<(AOS)/common/common.gyp:controls',
+ '<(DEPTH)/frc971/frc971.gyp:common',
+ '<(DEPTH)/frc971/control_loops/control_loops.gyp:state_feedback_loop',
+ '<(DEPTH)/frc971/queues/queues.gyp:queues',
+ ],
+ 'export_dependent_settings': [
+ '<(DEPTH)/frc971/control_loops/control_loops.gyp:state_feedback_loop',
+ '<(AOS)/common/common.gyp:controls',
+ 'drivetrain_loop',
+ ],
+ },
+ {
+ 'target_name': 'drivetrain_lib_test',
+ 'type': 'executable',
+ 'sources': [
+ 'drivetrain_lib_test.cc',
+ ],
+ 'dependencies': [
+ '<(EXTERNALS):gtest',
+ 'drivetrain_loop',
+ 'drivetrain_lib',
+ '<(AOS)/common/common.gyp:queue_testutils',
+ '<(DEPTH)/frc971/control_loops/control_loops.gyp:state_feedback_loop',
+ ],
+ },
+ {
+ 'target_name': 'drivetrain',
+ 'type': 'executable',
+ 'sources': [
+ 'drivetrain_main.cc',
+ ],
+ 'dependencies': [
+ '<(AOS)/atom_code/atom_code.gyp:init',
+ 'drivetrain_lib',
+ 'drivetrain_loop',
+ ],
+ },
+ ],
+}
diff --git a/frc971/control_loops/DriveTrain.h b/frc971/control_loops/drivetrain/drivetrain.h
similarity index 94%
rename from frc971/control_loops/DriveTrain.h
rename to frc971/control_loops/drivetrain/drivetrain.h
index 61ea524..7b07247 100644
--- a/frc971/control_loops/DriveTrain.h
+++ b/frc971/control_loops/drivetrain/drivetrain.h
@@ -2,7 +2,7 @@
#define FRC971_CONTROL_LOOPS_DRIVETRAIN_H_
#include "aos/common/control_loop/ControlLoop.h"
-#include "frc971/control_loops/DriveTrain.q.h"
+#include "frc971/control_loops/drivetrain/drivetrain.q.h"
namespace frc971 {
namespace control_loops {
diff --git a/frc971/control_loops/DriveTrain.q b/frc971/control_loops/drivetrain/drivetrain.q
similarity index 100%
rename from frc971/control_loops/DriveTrain.q
rename to frc971/control_loops/drivetrain/drivetrain.q
diff --git a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
new file mode 100644
index 0000000..934ba9d
--- /dev/null
+++ b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
@@ -0,0 +1,149 @@
+#include <unistd.h>
+
+#include <memory>
+
+#include "gtest/gtest.h"
+#include "aos/common/queue.h"
+#include "aos/common/queue_testutils.h"
+#include "frc971/control_loops/drivetrain/drivetrain.q.h"
+#include "frc971/control_loops/drivetrain/drivetrain.h"
+#include "frc971/control_loops/state_feedback_loop.h"
+#include "frc971/control_loops/drivetrain/drivetrain_motor_plant.h"
+
+
+using ::aos::time::Time;
+
+namespace frc971 {
+namespace control_loops {
+namespace testing {
+
+
+// Class which simulates the drivetrain and sends out queue messages containing the
+// position.
+class DrivetrainSimulation {
+ public:
+ // Constructs a motor simulation.
+ DrivetrainSimulation()
+ : drivetrain_plant_(
+ new StateFeedbackPlant<4, 2, 2>(MakeDrivetrainPlant())),
+ my_drivetrain_loop_(".frc971.control_loops.drivetrain",
+ 0x8a8dde77, ".frc971.control_loops.drivetrain.goal",
+ ".frc971.control_loops.drivetrain.position",
+ ".frc971.control_loops.drivetrain.output",
+ ".frc971.control_loops.drivetrain.status") {
+ Reinitialize();
+ }
+
+ // Resets the plant.
+ void Reinitialize() {
+ drivetrain_plant_->X(0, 0) = 0.0;
+ drivetrain_plant_->X(1, 0) = 0.0;
+ drivetrain_plant_->Y = drivetrain_plant_->C * drivetrain_plant_->X;
+ last_left_position_ = drivetrain_plant_->Y(0, 0);
+ last_right_position_ = drivetrain_plant_->Y(1, 0);
+ }
+
+ // Returns the position of the drivetrain.
+ double GetLeftPosition() const {
+ return drivetrain_plant_->Y(0, 0);
+ }
+ double GetRightPosition() const {
+ return drivetrain_plant_->Y(1, 0);
+ }
+
+ // Sends out the position queue messages.
+ void SendPositionMessage() {
+ const double left_encoder = GetLeftPosition();
+ const double right_encoder = GetRightPosition();
+
+ ::aos::ScopedMessagePtr<control_loops::Drivetrain::Position> position =
+ my_drivetrain_loop_.position.MakeMessage();
+ position->left_encoder = left_encoder;
+ position->right_encoder = right_encoder;
+ position.Send();
+ }
+
+ // Simulates the drivetrain moving for one timestep.
+ void Simulate() {
+ last_left_position_ = drivetrain_plant_->Y(0, 0);
+ last_right_position_ = drivetrain_plant_->Y(1, 0);
+ EXPECT_TRUE(my_drivetrain_loop_.output.FetchLatest());
+ drivetrain_plant_->U << my_drivetrain_loop_.output->left_voltage,
+ my_drivetrain_loop_.output->right_voltage;
+ drivetrain_plant_->Update();
+ }
+
+ ::std::unique_ptr<StateFeedbackPlant<4, 2, 2>> drivetrain_plant_;
+ private:
+ Drivetrain my_drivetrain_loop_;
+ double last_left_position_;
+ double last_right_position_;
+};
+
+class DrivetrainTest : public ::testing::Test {
+ protected:
+ ::aos::common::testing::GlobalCoreInstance my_core;
+
+ // Create a new instance of the test queue so that it invalidates the queue
+ // that it points to. Otherwise, we will have a pointer to shared memory that
+ // is no longer valid.
+ Drivetrain my_drivetrain_loop_;
+
+ // Create a loop and simulation plant.
+ DrivetrainLoop drivetrain_motor_;
+ DrivetrainSimulation drivetrain_motor_plant_;
+
+ DrivetrainTest() : my_drivetrain_loop_(".frc971.control_loops.drivetrain",
+ 0x8a8dde77,
+ ".frc971.control_loops.drivetrain.goal",
+ ".frc971.control_loops.drivetrain.position",
+ ".frc971.control_loops.drivetrain.output",
+ ".frc971.control_loops.drivetrain.status"),
+ drivetrain_motor_(&my_drivetrain_loop_),
+ drivetrain_motor_plant_() {
+ // Flush the robot state queue so we can use clean shared memory for this
+ // test.
+ ::aos::robot_state.Clear();
+ SendDSPacket(true);
+ }
+
+ void SendDSPacket(bool enabled) {
+ ::aos::robot_state.MakeWithBuilder().enabled(enabled)
+ .autonomous(false)
+ .team_id(971).Send();
+ ::aos::robot_state.FetchLatest();
+ }
+
+ void VerifyNearGoal() {
+ my_drivetrain_loop_.goal.FetchLatest();
+ my_drivetrain_loop_.position.FetchLatest();
+ EXPECT_NEAR(my_drivetrain_loop_.goal->left_goal,
+ drivetrain_motor_plant_.GetLeftPosition(),
+ 1e-2);
+ EXPECT_NEAR(my_drivetrain_loop_.goal->right_goal,
+ drivetrain_motor_plant_.GetRightPosition(),
+ 1e-2);
+ }
+
+ virtual ~DrivetrainTest() {
+ ::aos::robot_state.Clear();
+ }
+};
+
+// Tests that the drivetrain converges on a goal.
+TEST_F(DrivetrainTest, ConvergesCorrectly) {
+ my_drivetrain_loop_.goal.MakeWithBuilder().control_loop_driving(true)
+ .left_goal(-1.0)
+ .right_goal(1.0).Send();
+ for (int i = 0; i < 200; ++i) {
+ drivetrain_motor_plant_.SendPositionMessage();
+ drivetrain_motor_.Iterate();
+ drivetrain_motor_plant_.Simulate();
+ SendDSPacket(true);
+ }
+ VerifyNearGoal();
+}
+
+} // namespace testing
+} // namespace control_loops
+} // namespace frc971
diff --git a/frc971/control_loops/drivetrain/drivetrain_main.cc b/frc971/control_loops/drivetrain/drivetrain_main.cc
new file mode 100644
index 0000000..a6efbcd
--- /dev/null
+++ b/frc971/control_loops/drivetrain/drivetrain_main.cc
@@ -0,0 +1,11 @@
+#include "frc971/control_loops/drivetrain/drivetrain.h"
+
+#include "aos/aos_core.h"
+
+int main() {
+ ::aos::Init();
+ frc971::control_loops::DrivetrainLoop drivetrain;
+ drivetrain.Run();
+ ::aos::Cleanup();
+ return 0;
+}
diff --git a/frc971/control_loops/drivetrain/drivetrain_motor_plant.cc b/frc971/control_loops/drivetrain/drivetrain_motor_plant.cc
new file mode 100644
index 0000000..d4edba8
--- /dev/null
+++ b/frc971/control_loops/drivetrain/drivetrain_motor_plant.cc
@@ -0,0 +1,34 @@
+#include "frc971/control_loops/drivetrain/drivetrain_motor_plant.h"
+
+#include "frc971/control_loops/state_feedback_loop.h"
+
+namespace frc971 {
+namespace control_loops {
+
+
+StateFeedbackPlant<4, 2, 2> MakeDrivetrainPlant() {
+ Eigen::Matrix<double, 4, 4> A;
+ A << 1.0, 0.0081841122497, 0.0, -5.9473473594e-05, 0.0, 0.660289401132, 0.0, -0.0103071702002, 0.0, -5.9473473594e-05, 1.0, 0.0081841122497, 0.0, -0.0103071702002, 0.0, 0.660289401132;
+ Eigen::Matrix<double, 4, 2> B;
+ B << 0.000333031510271, 1.09073596255e-05, 0.0623024929693, 0.00189032194188, 1.09073596255e-05, 0.000333031510271, 0.00189032194188, 0.0623024929693;
+ Eigen::Matrix<double, 2, 4> C;
+ C << 1, 0, 0, 0, 0, 0, 1, 0;
+ Eigen::Matrix<double, 2, 2> D;
+ D << 0, 0, 0, 0;
+ Eigen::Matrix<double, 2, 1> U_max;
+ U_max << 12.0, 12.0;
+ Eigen::Matrix<double, 2, 1> U_min;
+ U_min << -12.0, -12.0;
+ return StateFeedbackPlant<4, 2, 2>(A, B, C, D, U_max, U_min);
+}
+
+StateFeedbackLoop<4, 2, 2> MakeDrivetrainLoop() {
+ Eigen::Matrix<double, 4, 2> L;
+ L << 1.50028940113, -0.0103071702002, 41.1373728147, -1.1627040905, -0.0103071702002, 1.50028940113, -1.1627040905, 41.1373728147;
+ Eigen::Matrix<double, 2, 4> K;
+ K << 48.196534943, -0.087263189034, -1.46233261597, -0.163410937407, -1.46233261597, -0.163410937407, 48.196534943, -0.087263189034;
+ return StateFeedbackLoop<4, 2, 2>(L, K, MakeDrivetrainPlant());
+}
+
+} // namespace frc971
+} // namespace control_loops
diff --git a/frc971/control_loops/drivetrain/drivetrain_motor_plant.h b/frc971/control_loops/drivetrain/drivetrain_motor_plant.h
new file mode 100644
index 0000000..af43b46
--- /dev/null
+++ b/frc971/control_loops/drivetrain/drivetrain_motor_plant.h
@@ -0,0 +1,16 @@
+#ifndef FRC971_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_MOTOR_PLANT_H_
+#define FRC971_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_MOTOR_PLANT_H_
+
+#include "frc971/control_loops/state_feedback_loop.h"
+
+namespace frc971 {
+namespace control_loops {
+
+StateFeedbackPlant<4, 2, 2> MakeDrivetrainPlant();
+
+StateFeedbackLoop<4, 2, 2> MakeDrivetrainLoop();
+
+} // namespace frc971
+} // namespace control_loops
+
+#endif // FRC971_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_MOTOR_PLANT_H_
diff --git a/frc971/control_loops/matlab/drivetrain_controller.m b/frc971/control_loops/matlab/drivetrain_controller.m
index 11a5a4b..b51af99 100644
--- a/frc971/control_loops/matlab/drivetrain_controller.m
+++ b/frc971/control_loops/matlab/drivetrain_controller.m
@@ -1,12 +1,18 @@
close all;
load 'drivetrain_spin_low'
load 'drivetrain_strait_low'
+% Max power amps of CIM or maybe half the mass of the robot in lbs or the whole robot in kg.
m = 68;
+% Must be in meters. Maybe width of robot (distance of center wheels from center).
rb = 0.617998644 / 2.0;
+% Moment of Inertia
J = 7;
stall_current = 133.0;
+% Resistance of the motor, divided by the number of motors.
R = 12.0 / stall_current / 4 / 0.43;
+% Motor Constant
Km = (12.0 - R * 2.7) / (4650.0 / 60.0 * 2.0 * pi);
+% Torque Constant
Kt = 0.008;
r = 0.04445; % 3.5 inches diameter
G_low = 60.0 / 15.0 * 50.0 / 15.0;
@@ -15,6 +21,9 @@
G = G_low;
+% must refer to how each side of the robot affects the other? Units of 1 / kg
+% I think that if the center of mass is in the center of the robot, then
+% msp will evaluate to 2/(mass of robot) and msn will evaluate to 0.
msp = (1.0 / m + rb ^ 2.0 / J);
msn = (1.0 / m - rb ^ 2.0 / J);
tc = -Km * Kt * G ^ 2.0 / (R * r ^ 2.0);
diff --git a/frc971/control_loops/python/drivetrain.py b/frc971/control_loops/python/drivetrain.py
new file mode 100755
index 0000000..e5a8217
--- /dev/null
+++ b/frc971/control_loops/python/drivetrain.py
@@ -0,0 +1,161 @@
+#!/usr/bin/python
+
+import control_loop
+import numpy
+import sys
+from matplotlib import pylab
+
+class Drivetrain(control_loop.ControlLoop):
+ def __init__(self):
+ super(Drivetrain, self).__init__("Drivetrain")
+ # Stall Torque in N m
+ self.stall_torque = 2.42
+ # Stall Current in Amps
+ self.stall_current = 133
+ # Free Speed in RPM. Used number from last year.
+ self.free_speed = 4650.0
+ # Free Current in Amps
+ self.free_current = 2.7
+ # Moment of inertia of the drivetrain in kg m^2
+ # Just borrowed from last year.
+ self.J = 7.0
+ # Mass of the robot, in kg.
+ self.m = 68
+ # Radius of the robot, in meters (from last year).
+ self.rb = 0.617998644 / 2.0
+ # Radius of the wheels, in meters.
+ self.r = .04445
+ # Resistance of the motor, divided by the number of motors.
+ self.R = 12.0 / self.stall_current / 6
+ # Motor velocity constant
+ self.Kv = ((self.free_speed / 60.0 * 2.0 * numpy.pi) /
+ (12.0 - self.R * self.free_current))
+ # Torque constant
+ self.Kt = self.stall_torque / self.stall_current
+ # Gear ratios
+ self.G_low = 16.0 / 60.0 * 19.0 / 50.0
+ self.G_high = 28.0 / 48.0 * 19.0 / 50.0
+ self.G = self.G_low
+ # Control loop time step
+ self.dt = 0.01
+
+ # These describe the way that a given side of a robot will be influenced
+ # by the other side. Units of 1 / kg.
+ self.msp = 1.0 / self.m + self.rb * self.rb / self.J
+ self.msn = 1.0 / self.m - self.rb * self.rb / self.J
+ # The calculations which we will need for A and B.
+ self.tc = -self.Kt / self.Kv / (self.G * self.G * self.R * self.r * self.r)
+ self.mp = self.Kt / (self.G * self.R * self.r)
+
+ # State feedback matrices
+ # X will be of the format
+ # [[position1], [velocity1], [position2], velocity2]]
+ self.A_continuous = numpy.matrix(
+ [[0, 1, 0, 0],
+ [0, self.msp * self.tc, 0, self.msn * self.tc],
+ [0, 0, 0, 1],
+ [0, self.msn * self.tc, 0, self.msp * self.tc]])
+ self.B_continuous = numpy.matrix(
+ [[0, 0],
+ [self.msp * self.mp, self.msn * self.mp],
+ [0, 0],
+ [self.msn * self.mp, self.msp * self.mp]])
+ self.C = numpy.matrix([[1, 0, 0, 0],
+ [0, 0, 1, 0]])
+ self.D = numpy.matrix([[0, 0],
+ [0, 0]])
+
+ self.ContinuousToDiscrete(self.A_continuous, self.B_continuous,
+ self.dt, self.C)
+
+ # Poles from last year.
+ self.hp = 0.8
+ self.lp = 0.85
+ self.PlaceControllerPoles([self.hp, self.hp, self.lp, self.lp])
+
+ print self.K
+
+ self.hlp = 0.07
+ self.llp = 0.09
+ self.PlaceObserverPoles([self.hlp, self.hlp, self.llp, self.llp])
+
+ self.U_max = numpy.matrix([[12.0], [12.0]])
+ self.U_min = numpy.matrix([[-12.0], [-12.0]])
+
+def main(argv):
+ # Simulate the response of the system to a step input.
+ drivetrain = Drivetrain()
+ simulated_left = []
+ simulated_right = []
+ for _ in xrange(100):
+ drivetrain.Update(numpy.matrix([[12.0], [12.0]]))
+ simulated_left.append(drivetrain.X[0, 0])
+ simulated_right.append(drivetrain.X[2, 0])
+
+ pylab.plot(range(100), simulated_left)
+ pylab.plot(range(100), simulated_right)
+ pylab.show()
+
+ # Simulate forwards motion.
+ drivetrain = Drivetrain()
+ close_loop_left = []
+ close_loop_right = []
+ R = numpy.matrix([[1.0], [0.0], [1.0], [0.0]])
+ for _ in xrange(100):
+ U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat),
+ drivetrain.U_min, drivetrain.U_max)
+ drivetrain.UpdateObserver(U)
+ drivetrain.Update(U)
+ close_loop_left.append(drivetrain.X[0, 0])
+ close_loop_right.append(drivetrain.X[2, 0])
+
+ pylab.plot(range(100), close_loop_left)
+ pylab.plot(range(100), close_loop_right)
+ pylab.show()
+
+ # Try turning in place
+ drivetrain = Drivetrain()
+ close_loop_left = []
+ close_loop_right = []
+ R = numpy.matrix([[-1.0], [0.0], [1.0], [0.0]])
+ for _ in xrange(100):
+ U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat),
+ drivetrain.U_min, drivetrain.U_max)
+ drivetrain.UpdateObserver(U)
+ drivetrain.Update(U)
+ close_loop_left.append(drivetrain.X[0, 0])
+ close_loop_right.append(drivetrain.X[2, 0])
+
+ pylab.plot(range(100), close_loop_left)
+ pylab.plot(range(100), close_loop_right)
+ pylab.show()
+
+ # Try turning just one side.
+ drivetrain = Drivetrain()
+ close_loop_left = []
+ close_loop_right = []
+ R = numpy.matrix([[0.0], [0.0], [1.0], [0.0]])
+ for _ in xrange(100):
+ U = numpy.clip(drivetrain.K * (R - drivetrain.X_hat),
+ drivetrain.U_min, drivetrain.U_max)
+ drivetrain.UpdateObserver(U)
+ drivetrain.Update(U)
+ close_loop_left.append(drivetrain.X[0, 0])
+ close_loop_right.append(drivetrain.X[2, 0])
+
+ pylab.plot(range(100), close_loop_left)
+ pylab.plot(range(100), close_loop_right)
+ pylab.show()
+
+ # Write the generated constants out to a file.
+ if len(argv) != 3:
+ print "Expected .h file name and .cc file name"
+ else:
+ if argv[1][-3:] == '.cc':
+ print '.cc file is second'
+ else:
+ drivetrain.DumpHeaderFile(argv[1])
+ drivetrain.DumpCppFile(argv[2], argv[1])
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv))