Add motor speed control

This commit is contained in:
root 2022-03-21 22:37:13 +00:00
parent 1a488065ac
commit 5d8df998ae
5 changed files with 138 additions and 44 deletions

View file

@ -4,6 +4,7 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include <chrono> #include <chrono>
#include <thread>
#include "include/robot.hpp" #include "include/robot.hpp"
@ -18,6 +19,11 @@ public:
NONE = 5 NONE = 5
}; };
enum Control : uint8_t {
POWER = 0,
SPEED = 1
};
static Motor& get_instance() static Motor& get_instance()
{ {
static Motor instance; static Motor instance;
@ -27,18 +33,27 @@ public:
Motor(Motor const&) = delete; Motor(Motor const&) = delete;
void operator=(Motor const&) = delete; void operator=(Motor const&) = delete;
static void power(uint8_t port, double percent); void set_speed(uint8_t port, double rpm);
static void pwm(uint8_t port, uint16_t pwm, Mode mode); void set_power(uint8_t port, double percent);
static void set_pwm(uint8_t port, uint16_t pwm, Mode mode);
std::vector<double> get_speed(); std::vector<double> get_speed();
private: private:
Motor(); Motor();
// Speed calculation and speed filter
int32_t last_encoder_values[MOTOR_COUNT] = {0}; int32_t last_encoder_values[MOTOR_COUNT] = {0};
double filtered_speeds[MOTOR_COUNT] = {0}; double filtered_speeds[MOTOR_COUNT] = {0};
std::chrono::system_clock::time_point last_time_read; std::chrono::system_clock::time_point last_time_encoders_read;
// Speed control
double speed_targets[MOTOR_COUNT] = {0};
double motor_control_modes[MOTOR_COUNT] = {0};
std::thread speed_control_thread;
void reset_speed(); void reset_speed();
void speed_control_loop();
}; };
#endif // COMPLIB_SERVER_MOTOR_HPP #endif // COMPLIB_SERVER_MOTOR_HPP

View file

@ -15,6 +15,11 @@
#define MOTOR_MAX_DELTA_TIME_S 0.1L #define MOTOR_MAX_DELTA_TIME_S 0.1L
#define MOTOR_FILTER_ALPHA 0.05L #define MOTOR_FILTER_ALPHA 0.05L
#define MOTOR_FILTER_RESET_DELAY_US (1000 * 10) #define MOTOR_FILTER_RESET_DELAY_US (1000 * 10)
#define MOTOR_SPEED_CONTROL_RESET_DELAY_S 0.01L
#define MOTOR_SPEED_CONTROL_LOOP_HZ 150
#define MOTOR_SPEED_CONTROL_KP 0.5L
#define MOTOR_SPEED_CONTROL_KI 5.0L
#define MOTOR_SPEED_CONTROL_KD 0.025L
#define LEFT_PORT 3 #define LEFT_PORT 3
#define RIGHT_PORT 0 #define RIGHT_PORT 0

View file

@ -7,7 +7,7 @@
//SPI_MODE_3 (1,1) CPOL = 1, CPHA = 1, Clock idle high, data is clocked in on rising, edge output data (change) on falling edge //SPI_MODE_3 (1,1) CPOL = 1, CPHA = 1, Clock idle high, data is clocked in on rising, edge output data (change) on falling edge
#define SPI_BUFFER_SIZE 20 #define SPI_BUFFER_SIZE 20
#define SPI_SPEED 4000000 // 4 MHz #define SPI_SPEED 2000000 // 2 MHz
#define SPI_BITS_PER_WORD 8 #define SPI_BITS_PER_WORD 8
class Spi { class Spi {

View file

@ -105,43 +105,46 @@ int main() {
// } // }
// double end_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); // double end_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
double kp = 0.5; // double kp = 0.5;
double ki = 5.0; // double ki = 5.0;
double kd = 0.025; // double kd = 0.025;
double setpoint = 0; double setpoint = 50;
double errror_sum = 0; // double errror_sum = 0;
double last_error = 0; // double last_error = 0;
// int port = 0; // int port = 0;
// long double accumulator = 0; // long double accumulator = 0;
// long double alpha = 0.2; // long double alpha = 0.2;
auto last_time = std::chrono::high_resolution_clock::now(); // auto last_time = std::chrono::high_resolution_clock::now();
auto start_time = last_time; // auto start_time = last_time;
int i = 0; // int i = 0;
while (1 == 1) { while (1 == 1) {
setpoint += 0.05; setpoint += 0.5;
auto current_time = std::chrono::high_resolution_clock::now(); // auto current_time = std::chrono::high_resolution_clock::now();
long double delta_seconds = std::chrono::duration_cast<std::chrono::microseconds>(current_time - last_time).count() / 1000000.0; // long double delta_seconds = std::chrono::duration_cast<std::chrono::microseconds>(current_time - last_time).count() / 1000000.0;
long double delta_seconds_total = std::chrono::duration_cast<std::chrono::microseconds>(current_time - start_time).count() / 1000000.0; // long double delta_seconds_total = std::chrono::duration_cast<std::chrono::microseconds>(current_time - start_time).count() / 1000000.0;
last_time = current_time; // last_time = current_time;
auto speeds = Motor::get_instance().get_speed(); // auto speeds = Motor::get_instance().get_speed();
double e = setpoint - speeds.at(0); // double e = setpoint - speeds.at(0);
errror_sum += e * delta_seconds; // errror_sum += e * delta_seconds;
double error_diff = (e - last_error) / delta_seconds; // double error_diff = (e - last_error) / delta_seconds;
last_error = e; // last_error = e;
double u = kp * e + errror_sum * ki + error_diff * kd; // double u = kp * e + errror_sum * ki + error_diff * kd;
Motor::power(0, std::min(std::max(-100.0, u), 100.0)); // Motor::power(0, std::min(std::max(-100.0, u), 100.0));
spdlog::info("{} {:05.0f} {:05.0f}", delta_seconds_total, setpoint, speeds.at(0)); // spdlog::info("{} {:05.0f} {:05.0f}", delta_seconds_total, setpoint, speeds.at(0));
usleep(1000.0 * (1000.0 / 150.0)); usleep(1000.0 * (1000.0 / 10.0));
Motor::get_instance().set_speed(0, setpoint);
i += 1; Motor::get_instance().set_speed(3, -setpoint);
// i += 1;
// if (i % 1000 == 0) { // if (i % 1000 == 0) {
// setpoint *= -1; // setpoint *= -1;
// } // }
auto speeds = Motor::get_instance().get_speed();
spdlog::info("Speed: {} {} Target: {}", speeds.at(0), speeds.at(3), setpoint);
} }
return 0; return 0;
} }

View file

@ -1,5 +1,6 @@
#include <stdexcept> #include <stdexcept>
#include <unistd.h> #include <unistd.h>
#include <chrono>
#include "include/motor.hpp" #include "include/motor.hpp"
#include "include/spi.hpp" #include "include/spi.hpp"
@ -7,18 +8,34 @@
#include "include/encoder.hpp" #include "include/encoder.hpp"
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
Motor::Motor() { void check_port(uint8_t port) {
reset_speed();
}
void Motor::power(uint8_t port, double percent) {
if (port >= MOTOR_COUNT) { if (port >= MOTOR_COUNT) {
throw std::invalid_argument("Invalid motor port specified"); throw std::invalid_argument("Invalid motor port specified");
} }
}
Motor::Motor() {
reset_speed();
speed_control_thread = std::thread(&Motor::speed_control_loop, this);
speed_control_thread.detach();
}
void Motor::set_speed(uint8_t port, double rpm) {
check_port(port);
speed_targets[port] = rpm;
motor_control_modes[port] = Motor::Control::SPEED;
}
void Motor::set_power(uint8_t port, double percent) {
check_port(port);
if (abs(percent) > 100) { if (abs(percent) > 100) {
throw std::invalid_argument("Invalid motor percent specified. Should be -100 <= percent <= 100"); throw std::invalid_argument("Invalid motor percent specified. Should be -100 <= percent <= 100");
} }
motor_control_modes[port] = Motor::Control::POWER;
Mode mode = Motor::Mode::COAST; Mode mode = Motor::Mode::COAST;
if (percent < 0) { if (percent < 0) {
mode = Motor::Mode::BACKWARD; mode = Motor::Mode::BACKWARD;
@ -27,14 +44,11 @@ void Motor::power(uint8_t port, double percent) {
} }
uint16_t pwm = abs(percent) * (MAX_MOTOR_SPEED / 100.0); uint16_t pwm = abs(percent) * (MAX_MOTOR_SPEED / 100.0);
Motor::pwm(port, pwm, mode); Motor::set_pwm(port, pwm, mode);
}
void Motor::pwm(uint8_t port, uint16_t pwm, Mode mode) {
if (port >= MOTOR_COUNT) {
throw std::invalid_argument("Invalid motor port specified");
} }
void Motor::set_pwm(uint8_t port, uint16_t pwm, Mode mode) {
check_port(port);
if (mode > Motor::Mode::BREAK) { if (mode > Motor::Mode::BREAK) {
throw std::invalid_argument("Invalid motor mode specified"); throw std::invalid_argument("Invalid motor mode specified");
} }
@ -56,9 +70,9 @@ void Motor::pwm(uint8_t port, uint16_t pwm, Mode mode) {
std::vector<double> Motor::get_speed() { std::vector<double> Motor::get_speed() {
auto current_time = std::chrono::high_resolution_clock::now(); auto current_time = std::chrono::high_resolution_clock::now();
long double delta_seconds = std::chrono::duration_cast<std::chrono::microseconds>(current_time - last_time_read).count() / 1000000.0; long double delta_seconds = std::chrono::duration_cast<std::chrono::microseconds>(current_time - last_time_encoders_read).count() / 1000000.0;
last_time_read = current_time; last_time_encoders_read = current_time;
if (delta_seconds > MOTOR_MAX_DELTA_TIME_S) { if (delta_seconds > MOTOR_MAX_DELTA_TIME_S) {
reset_speed(); reset_speed();
@ -82,7 +96,7 @@ std::vector<double> Motor::get_speed() {
} }
void Motor::reset_speed() { void Motor::reset_speed() {
last_time_read = std::chrono::high_resolution_clock::now(); last_time_encoders_read = std::chrono::high_resolution_clock::now();
auto current_ticks = Encoder::read_all(); auto current_ticks = Encoder::read_all();
for (int i = 0; i < MOTOR_COUNT; i++) { for (int i = 0; i < MOTOR_COUNT; i++) {
@ -90,3 +104,60 @@ void Motor::reset_speed() {
last_encoder_values[i] = current_ticks.at(i); last_encoder_values[i] = current_ticks.at(i);
} }
} }
void Motor::speed_control_loop() {
double errror_sums[MOTOR_COUNT] = {0};
double last_errors[MOTOR_COUNT] = {0};
auto last_run = std::chrono::high_resolution_clock::now();
while (1 == 1) {
std::this_thread::sleep_for(std::chrono::milliseconds(1000 / MOTOR_SPEED_CONTROL_LOOP_HZ));
bool should_control_speed = false;
for (int i = 0; i < MOTOR_COUNT; i++) {
if (motor_control_modes[i] == Motor::Control::SPEED) {
should_control_speed = true;
}
}
if (!should_control_speed) {
continue;
}
auto current_time = std::chrono::high_resolution_clock::now();
long double delta_seconds = std::chrono::duration_cast<std::chrono::microseconds>(current_time - last_run).count() / 1000000.0;
last_run = current_time;
if (delta_seconds > MOTOR_SPEED_CONTROL_RESET_DELAY_S) {
std::fill_n(errror_sums, MOTOR_COUNT, 0);
std::fill_n(last_errors, MOTOR_COUNT, 0);
continue;
}
auto speeds = Motor::get_instance().get_speed();
for (int i = 0; i < MOTOR_COUNT; i++) {
if (motor_control_modes[i] == Motor::Control::SPEED) {
// P
double error = speed_targets[i] - speeds.at(i);
// I
errror_sums[i] += error * delta_seconds;
// D
if (last_errors[i] == 0) { // Needed to prevent spike on first loop iteration
last_errors[i] = error;
}
double error_diff = (error - last_errors[i]) / delta_seconds;
double power = MOTOR_SPEED_CONTROL_KP * error +
errror_sums[i] * MOTOR_SPEED_CONTROL_KI +
error_diff * MOTOR_SPEED_CONTROL_KD;
set_power(i, std::min(std::max(-100.0, power), 100.0));
// spdlog::info("{:03.2f} {:03.2f} {:03.2f} {:03.2f} {:03.2f} {:03.2f} {:03.2f}",
// speed_targets[i], speeds.at(i), error, delta_seconds, last_errors[i], error_diff, power);
last_errors[i] = error;
}
}
}
}