145 lines
5.4 KiB
Python
145 lines
5.4 KiB
Python
"""This file implements an accurate motor model."""
|
|
|
|
import numpy as np
|
|
VOLTAGE_CLIPPING = 50
|
|
# TODO(b/73728631): Clamp the pwm signal instead of the OBSERVED_TORQUE_LIMIT.
|
|
OBSERVED_TORQUE_LIMIT = 5.7
|
|
MOTOR_VOLTAGE = 16.0
|
|
MOTOR_RESISTANCE = 0.186
|
|
MOTOR_TORQUE_CONSTANT = 0.0954
|
|
MOTOR_VISCOUS_DAMPING = 0
|
|
MOTOR_SPEED_LIMIT = MOTOR_VOLTAGE / (
|
|
MOTOR_VISCOUS_DAMPING + MOTOR_TORQUE_CONSTANT)
|
|
NUM_MOTORS = 8
|
|
|
|
|
|
class MotorModel(object):
|
|
"""The accurate motor model, which is based on the physics of DC motors.
|
|
|
|
The motor model support two types of control: position control and torque
|
|
control. In position control mode, a desired motor angle is specified, and a
|
|
torque is computed based on the internal motor model. When the torque control
|
|
is specified, a pwm signal in the range of [-1.0, 1.0] is converted to the
|
|
torque.
|
|
|
|
The internal motor model takes the following factors into consideration:
|
|
pd gains, viscous friction, back-EMF voltage and current-torque profile.
|
|
"""
|
|
|
|
def __init__(self, torque_control_enabled=False, kp=1.2, kd=0):
|
|
self._torque_control_enabled = torque_control_enabled
|
|
self._kp = kp
|
|
self._kd = kd
|
|
self._resistance = MOTOR_RESISTANCE
|
|
self._voltage = MOTOR_VOLTAGE
|
|
self._torque_constant = MOTOR_TORQUE_CONSTANT
|
|
self._viscous_damping = MOTOR_VISCOUS_DAMPING
|
|
self._current_table = [0, 10, 20, 30, 40, 50, 60]
|
|
self._torque_table = [0, 1, 1.9, 2.45, 3.0, 3.25, 3.5]
|
|
self._strength_ratios = [1.0] * NUM_MOTORS
|
|
|
|
def set_strength_ratios(self, ratios):
|
|
"""Set the strength of each motors relative to the default value.
|
|
|
|
Args:
|
|
ratios: The relative strength of motor output. A numpy array ranging from
|
|
0.0 to 1.0.
|
|
"""
|
|
self._strength_ratios = np.array(ratios)
|
|
|
|
def set_motor_gains(self, kp, kd):
|
|
"""Set the gains of all motors.
|
|
|
|
These gains are PD gains for motor positional control. kp is the
|
|
proportional gain and kd is the derivative gain.
|
|
|
|
Args:
|
|
kp: proportional gain of the motors.
|
|
kd: derivative gain of the motors.
|
|
"""
|
|
self._kp = kp
|
|
self._kd = kd
|
|
|
|
def set_voltage(self, voltage):
|
|
self._voltage = voltage
|
|
|
|
def get_voltage(self):
|
|
return self._voltage
|
|
|
|
def set_viscous_damping(self, viscous_damping):
|
|
self._viscous_damping = viscous_damping
|
|
|
|
def get_viscous_dampling(self):
|
|
return self._viscous_damping
|
|
|
|
def convert_to_torque(self,
|
|
motor_commands,
|
|
motor_angle,
|
|
motor_velocity,
|
|
true_motor_velocity,
|
|
kp=None,
|
|
kd=None):
|
|
"""Convert the commands (position control or torque control) to torque.
|
|
|
|
Args:
|
|
motor_commands: The desired motor angle if the motor is in position
|
|
control mode. The pwm signal if the motor is in torque control mode.
|
|
motor_angle: The motor angle observed at the current time step. It is
|
|
actually the true motor angle observed a few milliseconds ago (pd
|
|
latency).
|
|
motor_velocity: The motor velocity observed at the current time step, it
|
|
is actually the true motor velocity a few milliseconds ago (pd latency).
|
|
true_motor_velocity: The true motor velocity. The true velocity is used
|
|
to compute back EMF voltage and viscous damping.
|
|
kp: Proportional gains for the motors' PD controllers. If not provided, it
|
|
uses the default kp of the minitaur for all the motors.
|
|
kd: Derivative gains for the motors' PD controllers. If not provided, it
|
|
uses the default kp of the minitaur for all the motors.
|
|
|
|
Returns:
|
|
actual_torque: The torque that needs to be applied to the motor.
|
|
observed_torque: The torque observed by the sensor.
|
|
"""
|
|
if self._torque_control_enabled:
|
|
pwm = motor_commands
|
|
else:
|
|
if kp is None:
|
|
kp = np.full(NUM_MOTORS, self._kp)
|
|
if kd is None:
|
|
kd = np.full(NUM_MOTORS, self._kd)
|
|
pwm = -1 * kp * (motor_angle - motor_commands) - kd * motor_velocity
|
|
|
|
pwm = np.clip(pwm, -1.0, 1.0)
|
|
return self._convert_to_torque_from_pwm(pwm, true_motor_velocity)
|
|
|
|
def _convert_to_torque_from_pwm(self, pwm, true_motor_velocity):
|
|
"""Convert the pwm signal to torque.
|
|
|
|
Args:
|
|
pwm: The pulse width modulation.
|
|
true_motor_velocity: The true motor velocity at the current moment. It is
|
|
used to compute the back EMF voltage and the viscous damping.
|
|
Returns:
|
|
actual_torque: The torque that needs to be applied to the motor.
|
|
observed_torque: The torque observed by the sensor.
|
|
"""
|
|
observed_torque = np.clip(
|
|
self._torque_constant *
|
|
(np.asarray(pwm) * self._voltage / self._resistance),
|
|
-OBSERVED_TORQUE_LIMIT, OBSERVED_TORQUE_LIMIT)
|
|
|
|
# Net voltage is clipped at 50V by diodes on the motor controller.
|
|
voltage_net = np.clip(
|
|
np.asarray(pwm) * self._voltage -
|
|
(self._torque_constant + self._viscous_damping) *
|
|
np.asarray(true_motor_velocity), -VOLTAGE_CLIPPING, VOLTAGE_CLIPPING)
|
|
current = voltage_net / self._resistance
|
|
current_sign = np.sign(current)
|
|
current_magnitude = np.absolute(current)
|
|
# Saturate torque based on empirical current relation.
|
|
actual_torque = np.interp(current_magnitude, self._current_table,
|
|
self._torque_table)
|
|
actual_torque = np.multiply(current_sign, actual_torque)
|
|
actual_torque = np.multiply(self._strength_ratios, actual_torque)
|
|
return actual_torque, observed_torque
|