MotorGo Mini
MotorGo Mini
The MotorGo Mini is a small, low-cost, low-power motor controller that can be used to control a two motors.
It’s pretty sweet and the component provides the implementation of the two channel FOC motor controller, along with other peripheral classes such as the ADC, LEDs, and I2C.
API Reference
Header File
Classes
-
class MotorGoMini : public espp::BaseComponent
This class acts as a board support component for the MotorGo-Mini board. It provides a high-level interface to the board’s functionality. The MotorGo-Mini board is a motor driver board that can drive two DC motors. More information about the board can be found at: https://github.com/Every-Flavor-Robotics/motorgo-mini-hardware
High level overview of the board:
ESP32s3 module
Two LEDs (yellow and red)
Two BLDC motor drivers (TMC6300)
Two magnetic encoders (connected via two JST-SH 5 pin connectors to EncoderGo boards) (SPI2_HOST)
Two Qwiic connectors for I2C communication (I2C0)
Current sense resistors for each motor on the U and W phases NOTE: on the MotorGo-Mini v1.3, the M2 motor sense is swapped in the schematic, with U and W swapped
The class is a singleton and can be accessed using the get() method.
MotorGo-Mini Example
auto &motorgo_mini = espp::MotorGoMini::get(); motorgo_mini.set_log_level(espp::Logger::Verbosity::INFO); // set the configuration for the motors For simplicity, we'll copy the // defaults and modify them, but you can also just make a type of // espp::MotorGoMini::BldcMotor::Config auto motor1_config = motorgo_mini.default_motor1_config; motor1_config.phase_resistance = 4.0f; // ohms motor1_config.current_limit = 1.0f; // amps // velocity PID config: motor1_config.velocity_pid_config.kp = 0.010f; motor1_config.velocity_pid_config.ki = 0.100f; motor1_config.velocity_pid_config.kd = 0.000f; // angle PID config: motor1_config.angle_pid_config.kp = 5.000f; motor1_config.angle_pid_config.ki = 1.000f; motor1_config.angle_pid_config.kd = 0.000f; auto motor2_config = motorgo_mini.default_motor2_config; motor2_config.phase_resistance = 4.0f; // ohms motor2_config.current_limit = 1.0f; // amps // velocity PID config: motor2_config.velocity_pid_config.kp = 0.010f; motor2_config.velocity_pid_config.ki = 0.100f; motor2_config.velocity_pid_config.kd = 0.000f; // angle PID config: motor2_config.angle_pid_config.kp = 5.000f; motor2_config.angle_pid_config.ki = 1.000f; motor2_config.angle_pid_config.kd = 0.000f; // now initialize the motors motorgo_mini.init_motor_channel_1(motor1_config); motorgo_mini.init_motor_channel_2(motor2_config); // get the motor objects (shared pointers) for use in the script auto motor1 = motorgo_mini.motor1(); auto motor2 = motorgo_mini.motor2(); static constexpr uint64_t core_update_period_us = 1'000; // microseconds static constexpr float core_update_period = core_update_period_us / 1e6f; // seconds // static auto motion_control_type = espp::detail::MotionControlType::VELOCITY; static auto motion_control_type = espp::detail::MotionControlType::ANGLE; logger.info("Setting motion control type to {}", motion_control_type); motor1->set_motion_control_type(motion_control_type); motor2->set_motion_control_type(motion_control_type); motor1->enable(); motor2->enable(); std::atomic<float> target1 = 60.0f; std::atomic<float> target2 = 60.0f; static bool target_is_angle = motion_control_type == espp::detail::MotionControlType::ANGLE || motion_control_type == espp::detail::MotionControlType::ANGLE_OPENLOOP; // Function for initializing the target based on the motion control type auto initialize_target = [&]() { if (target_is_angle) { target1 = motor1->get_shaft_angle(); target2 = motor2->get_shaft_angle(); } else { target1 = 50.0f * espp::RPM_TO_RADS; target2 = 50.0f * espp::RPM_TO_RADS; } }; // run it once initialize_target(); auto dual_motor_fn = [&]() -> bool { motor1->loop_foc(); motor1->move(target1); motor2->loop_foc(); motor2->move(target2); return false; // don't want to stop the task }; auto dual_motor_timer = espp::HighResolutionTimer({.name = "Motor Timer", .callback = dual_motor_fn, .log_level = espp::Logger::Verbosity::WARN}); dual_motor_timer.periodic(core_update_period_us); static constexpr float sample_freq_hz = 100.0f; static constexpr float filter_cutoff_freq_hz = 5.0f; static constexpr float normalized_cutoff_frequency = 2.0f * filter_cutoff_freq_hz / sample_freq_hz; static constexpr size_t ORDER = 2; // NOTE: using the Df2 since it's hardware accelerated :) using Filter = espp::ButterworthFilter<ORDER, espp::BiquadFilterDf2>; Filter filter1({.normalized_cutoff_frequency = normalized_cutoff_frequency}); Filter filter2({.normalized_cutoff_frequency = normalized_cutoff_frequency}); // if it's a velocity setpoint then target is RPM fmt::print("%time(s), " "motor 1 target, " // target is either RPM or radians "motor 1 angle (radians), " "motor 1 speed (rpm), " "motor 2 target, " // target is either RPM or radians "motor 2 angle (radians), " "motor 2 speed (rpm)\n"); // make the task to periodically poll the encoders and print the state. NOTE: // the encoders run their own tasks to maintain state, so we're just polling // the current state. auto logging_fn = [&](std::mutex &m, std::condition_variable &cv) { static auto start = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now(); auto seconds = std::chrono::duration<float>(now - start).count(); auto _target1 = target1.load(); if (!target_is_angle) _target1 *= espp::RADS_TO_RPM; auto _target2 = target2.load(); if (!target_is_angle) _target2 *= espp::RADS_TO_RPM; auto rpm1 = filter1(motor1->get_shaft_velocity() * espp::RADS_TO_RPM); auto rpm2 = filter2(motor2->get_shaft_velocity() * espp::RADS_TO_RPM); auto rads1 = motor1->get_shaft_angle(); auto rads2 = motor2->get_shaft_angle(); fmt::print("{:.3f}, {:.3f}, {:.3f}, {:.3f}, {:.3f}, {:.3f}, {:.3f}\n", seconds, _target1, rads1, rpm1, _target2, rads2, rpm2); // NOTE: sleeping in this way allows the sleep to exit early when the // task is being stopped / destroyed { std::unique_lock<std::mutex> lk(m); cv.wait_for(lk, 10ms); } // don't want to stop the task return false; }; auto logging_task = espp::Task({.callback = logging_fn, .task_config = { .name = "Logging Task", .stack_size_bytes = 5 * 1024, }, .log_level = espp::Logger::Verbosity::WARN}); logging_task.start(); std::this_thread::sleep_for(1s); logger.info("Starting target task"); enum class IncrementDirection { DOWN = -1, HOLD = 0, UP = 1 }; static IncrementDirection increment_direction1 = IncrementDirection::UP; static IncrementDirection increment_direction2 = IncrementDirection::DOWN; auto update_target = [&](auto &target, auto &increment_direction) { float max_target = target_is_angle ? (2.0f * M_PI) : (200.0f * espp::RPM_TO_RADS); float target_delta = target_is_angle ? (M_PI / 4.0f) : (50.0f * espp::RPM_TO_RADS * core_update_period); // update target if (increment_direction == IncrementDirection::UP) { target += target_delta; if (target >= max_target) { increment_direction = IncrementDirection::DOWN; } } else if (increment_direction == IncrementDirection::DOWN) { target -= target_delta; if (target <= -max_target) { increment_direction = IncrementDirection::UP; } } }; // make a task which will update the target (velocity or angle) auto target_task_fn = [&](std::mutex &m, std::condition_variable &cv) { auto delay = std::chrono::duration<float>(target_is_angle ? 1.0f : core_update_period); auto start = std::chrono::high_resolution_clock::now(); update_target(target1, increment_direction1); update_target(target2, increment_direction2); // NOTE: sleeping in this way allows the sleep to exit early when the // task is being stopped / destroyed { std::unique_lock<std::mutex> lk(m); cv.wait_until(lk, start + delay); } // don't want to stop the task return false; }; auto target_task = espp::Task({ .callback = target_task_fn, .task_config = {.name = "Target Task"}, }); target_task.start(); logger.info("Initializing the button"); auto on_button_pressed = [&](const auto &event) { if (event.active) { logger.info("Button pressed, changing motion control type"); // switch between ANGLE and VELOCITY if (motion_control_type == espp::detail::MotionControlType::ANGLE || motion_control_type == espp::detail::MotionControlType::ANGLE_OPENLOOP) { motion_control_type = espp::detail::MotionControlType::VELOCITY; target_is_angle = false; } else { motion_control_type = espp::detail::MotionControlType::ANGLE; target_is_angle = true; } initialize_target(); motor1->set_motion_control_type(motion_control_type); motor2->set_motion_control_type(motion_control_type); } else { logger.info("Button released"); } }; motorgo_mini.initialize_button(on_button_pressed); // now loop forever (do nothing) while (true) { std::this_thread::sleep_for(1s); }
Public Types
-
using button_callback_t = espp::Interrupt::event_callback_fn
Alias for the button callback function.
-
using BldcMotor = espp::BldcMotor<espp::BldcDriver, Encoder>
Alias for the BLDC motor type.
-
using MotorDriver = espp::BldcDriver
Alias for the 6-PWM BLDC motor driver helper used on each motor channel.
Note
Provided for API symmetry with espp::MotorGoAxis.
-
using VelocityFilter = espp::SimpleLowpassFilter
Alias for the velocity filter type.
-
using AngleFilter = espp::SimpleLowpassFilter
Alias for the angle filter type.
Public Functions
-
I2c &get_external_i2c()
Get a reference to the external I2C bus
- Returns:
A reference to the external I2C bus
-
espp::Interrupt &interrupts()
Get a reference to the interrupts
- Returns:
A reference to the interrupts
-
bool initialize_button(const button_callback_t &callback = nullptr)
Initialize the button
- Parameters:
callback – The callback function to call when the button is pressed
- Returns:
true if the button was successfully initialized, false otherwise
-
bool button_state() const
Get the button state
- Returns:
The button state (true = button pressed, false = button released)
-
espp::Led::ChannelConfig &yellow_led()
Get a reference to the yellow LED channel (channel 0)
- Returns:
A reference to the yellow LED channel (channel 0)
-
espp::Led::ChannelConfig &led_channel0()
Get a reference to the yellow LED channel (channel 0)
- Returns:
A reference to the yellow LED channel (channel 0)
-
void set_yellow_led_duty(float duty)
Set the duty cycle of the yellow LED
Note
The duty cycle is a percentage of the maximum duty cycle (which is 100.0) 0.0 is off, 100.0 is fully on
Note
For this function to have an effect, the LED timer must NOT be running (i.e. stop_breathing() must be called)
- Parameters:
duty – The duty cycle of the yellow LED (0.0 - 100.0)
-
espp::Led::ChannelConfig &red_led()
Get a reference to the red LED channel (channel 1)
- Returns:
A reference to the red LED channel (channel 1)
-
espp::Led::ChannelConfig &led_channel1()
Get a reference to the red LED channel (channel 1)
- Returns:
A reference to the red LED channel (channel 1)
-
void set_red_led_duty(float duty)
Set the duty cycle of the red LED
Note
The duty cycle is a percentage of the maximum duty cycle (which is 100.0) 0.0 is off, 100.0 is fully on
Note
For this function to have an effect, the LED timer must NOT be running (i.e. stop_breathing() must be called)
- Parameters:
duty – The duty cycle of the red LED (0.0 - 100.0)
-
espp::Led &led()
Get a reference to the LED object which controls the LEDs
- Returns:
A reference to the Led object
-
espp::Gaussian &gaussian()
Get a reference to the Gaussian object which is used for breathing the LEDs.
- Returns:
A reference to the Gaussian object
-
void start_breathing()
Start breathing the LEDs
This function starts the LED timer which will periodically update the LED duty cycle using the gaussian to create a breathing efect.
-
void stop_breathing()
Stop breathing the LEDs
This function stops the LED timer which will stop updating the LED duty cycle. It will also set the LED duty cycle to 0, effectively turning off the LEDs.
-
espp::OneshotAdc &adc1()
Get a reference to the ADC_UNIT_1 OneshotAdc object
- Returns:
A reference to the ADC_UNIT_1 OneshotAdc object
-
espp::OneshotAdc &adc2()
Get a reference to the ADC_UNIT_2 OneshotAdc object
- Returns:
A reference to the ADC_UNIT_2 OneshotAdc object
- void init_motor_channel_1 (const BldcMotor::Config &motor_config, const DriverConfig &driver_config={.power_supply_voltage=5.0f,.limit_voltage=5.0f})
Initialize the MotorGo-Mini’s components for motor channel 1
This function initializes the encoder, driver, and motor for motor channel 1. This consists of initializing encoder1, motor_driver, and motor1.
- Parameters:
motor_config – The motor configuration
driver_config – The driver configuration
- void init_motor_channel_2 (const BldcMotor::Config &motor_config, const DriverConfig &driver_config={.power_supply_voltage=5.0f,.limit_voltage=5.0f})
Initialize the MotorGo-Mini’s components for motor channel 2
This function initializes the encoder, driver and motor for motor channel 2. This consists of initializing encoder2, motor2_driver, and motor2.
- Parameters:
motor_config – The motor configuration
driver_config – The driver configuration
-
std::shared_ptr<espp::BldcDriver> motor1_driver()
Get a shared pointer to the motor 1 driver
- Returns:
A shared pointer to the motor 1 driver
-
std::shared_ptr<espp::BldcDriver> motor2_driver()
Get a shared pointer to the motor 2 driver
- Returns:
A shared pointer to the motor 2 driver
-
std::shared_ptr<BldcMotor> motor1()
Get a shared pointer to the motor 1
- Returns:
A shared pointer to the motor 1
-
std::shared_ptr<BldcMotor> motor2()
Get a shared pointer to the motor 2
- Returns:
A shared pointer to the motor 2
-
VelocityFilter &motor1_velocity_filter()
Get a reference to the motor 1 velocity filter
- Returns:
A reference to the motor 1 velocity filter
-
AngleFilter &motor1_angle_filter()
Get a reference to the motor 1 angle filter
- Returns:
A reference to the motor 1 angle filter
-
VelocityFilter &motor2_velocity_filter()
Get a reference to the motor 2 velocity filter
- Returns:
A reference to the motor 2 velocity filter
-
AngleFilter &motor2_angle_filter()
Get a reference to the motor 2 angle filter
- Returns:
A reference to the motor 2 angle filter
-
std::shared_ptr<Encoder> encoder1()
Get a shared pointer to the encoder 1
- Returns:
A shared pointer to the encoder 1
-
std::shared_ptr<Encoder> encoder2()
Get a shared pointer to the encoder 2
- Returns:
A shared pointer to the encoder 2
-
void reset_encoder1_accumulator()
Reset the encoder 1 accumulator
This function resets the encoder 1 accumulator to 0. This will reset the encoder’s position to be within the range of 0 to 2*pi.
-
void reset_encoder2_accumulator()
Reset the encoder 2 accumulator
This function resets the encoder 2 accumulator to 0. This will reset the encoder’s position to be within the range of 0 to 2*pi.
-
bool initialize_encoders(bool run_tasks = true)
Initialize the magnetic encoders for both motor channels.
Encoders that are already initialized are left untouched.
- Parameters:
run_tasks – If true, each encoder starts its own update task.
- Returns:
True if both encoders were initialized successfully.
-
bool initialize_motors(float power_supply_voltage = driver_default_power_supply_voltage(), float limit_voltage = driver_default_voltage_limit(), uint64_t dead_zone_ns = default_motor_dead_zone_ns())
Initialize the BLDC motor drivers for both motor channels.
Drivers that are already initialized are left untouched.
- Parameters:
power_supply_voltage – Supply voltage (V) provided to the drivers.
limit_voltage – Maximum voltage (V) allowed to the motors.
dead_zone_ns – Dead-time applied to both sides of each phase PWM.
- Returns:
True if both drivers were initialized successfully.
-
std::shared_ptr<MotorDriver> motor_driver(size_t index)
Get one motor driver helper.
- Parameters:
index – Zero-based motor index in the range [0, num_motor_channels()).
- Returns:
Shared pointer to the requested driver, or `nullptr` if the index is invalid or that driver has not been initialized.
-
std::shared_ptr<Encoder> encoder(size_t index)
Get one encoder helper.
- Parameters:
index – Zero-based encoder index in the range [0, num_motor_channels()).
- Returns:
Shared pointer to the requested encoder, or `nullptr` if the index is invalid or that encoder has not been initialized.
-
void reset_encoder_accumulator(size_t index)
Reset one encoder’s accumulator.
- Parameters:
index – Zero-based encoder index in the range [0, num_motor_channels()).
-
bool motor_driver_enabled(size_t index)
Check whether one motor driver helper is currently enabled.
- Parameters:
index – Zero-based motor index in the range [0, num_motor_channels()).
- Returns:
True if the requested driver exists and is enabled.
-
bool enable_motor_driver(size_t index)
Enable one motor driver helper.
- Parameters:
index – Zero-based motor index in the range [0, num_motor_channels()).
- Returns:
True if the driver exists and was enabled.
-
void disable_motor_driver(size_t index)
Disable one motor driver helper.
- Parameters:
index – Zero-based motor index in the range [0, num_motor_channels()).
-
void enable_all_motor_drivers()
Enable all initialized motor driver helpers.
-
void disable_all_motor_drivers()
Disable all initialized motor driver helpers.
-
BldcMotor::Config default_motor_config(size_t index) const
Get a default FOC motor configuration for one channel.
- Parameters:
index – Zero-based motor index in the range [0, num_motor_channels()).
- Returns:
A BldcMotor::Config pre-populated with the board defaults for that channel (including the per-channel velocity / angle filters).
-
std::shared_ptr<BldcMotor> initialize_motor(size_t index, const BldcMotor::Config &config)
Create the FOC motor controller for one channel.
If the encoder / driver for the channel have not been initialized yet, this initializes them first, so a single call is enough to get a ready-to-use motor.
- Parameters:
index – Zero-based motor index in the range [0, num_motor_channels()).
config – The motor configuration. Its driver and sensor fields are overridden with the board’s per-channel driver and encoder.
- Returns:
Shared pointer to the created (and initialized) BldcMotor, or `nullptr` if the index is invalid or the channel’s driver/encoder could not be initialized.
-
std::shared_ptr<BldcMotor> motor(size_t index)
Get one FOC motor controller.
- Parameters:
index – Zero-based motor index in the range [0, num_motor_channels()).
- Returns:
Shared pointer to the requested motor, or `nullptr` if the index is invalid or the motor has not been initialized.
-
float motor1_current_u_amps()
Get the current sense value for motor 1 phase U
- Returns:
The current sense value for motor 1 phase U in amps
-
float motor1_current_w_amps()
Get the current sense value for motor 1 phase W
- Returns:
The current sense value for motor 1 phase W in amps
-
float motor2_current_u_amps()
Get the current sense value for motor 2 phase U
- Returns:
The current sense value for motor 2 phase U in amps
-
float motor2_current_w_amps()
Get the current sense value for motor 2 phase W
- Returns:
The current sense value for motor 2 phase W in amps
-
inline const std::string &get_name() const
Get the name of the component
Note
This is the tag of the logger
- Returns:
A const reference to the name of the component
-
inline void set_log_tag(const std::string_view &tag)
Set the tag for the logger
- Parameters:
tag – The tag to use for the logger
-
inline espp::Logger::Verbosity get_log_level() const
Get the log level for the logger
See also
See also
- Returns:
The verbosity level of the logger
-
inline void set_log_level(espp::Logger::Verbosity level)
Set the log level for the logger
See also
See also
- Parameters:
level – The verbosity level to use for the logger
-
inline void set_log_verbosity(espp::Logger::Verbosity level)
Set the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls set_log_level
- Parameters:
level – The verbosity level to use for the logger
-
inline espp::Logger::Verbosity get_log_verbosity() const
Get the log verbosity for the logger
See also
See also
See also
Note
This is a convenience method that calls get_log_level
- Returns:
The verbosity level of the logger
-
inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)
Set the rate limit for the logger
See also
Note
Only calls to the logger that have _rate_limit suffix will be rate limited
- Parameters:
rate_limit – The rate limit to use for the logger
Public Members
Public Static Functions
-
static inline MotorGoMini &get()
Access the singleton instance of the MotorGoMini class.
- Returns:
Reference to the singleton instance of the MotorGoMini class
-
static inline constexpr size_t num_motor_channels()
Get the number of supported motor channels.
- Returns:
The number of motor channels exposed by the board.
-
static inline constexpr float driver_default_power_supply_voltage()
Get the default motor supply voltage used for the board definition.
- Returns:
Default power supply voltage in volts.
-
static inline constexpr float driver_default_voltage_limit()
Get the default motor voltage limit used for the board definition.
- Returns:
Default motor voltage limit in volts.
-
static inline constexpr uint64_t default_motor_dead_zone_ns()
Get the default BLDC driver dead-time.
- Returns:
Default dead-time in nanoseconds.
-
struct DriverConfig
Driver Configuration for the MotorGo-Mini Motor Driver(s)