Simplify and enhance unit conversion

- Move unit/step conversion to modules/axisunit.h
- Unify motion::unitToAxisUnit<> and motion::unitToSteps<>,
  making conversion in other modules just as easy as motion.
- Improve the documentation
pull/71/head
Yuri D'Elia 2021-07-25 22:25:48 +02:00
parent 187858d228
commit b133c8b975
3 changed files with 87 additions and 51 deletions

View File

@ -20,11 +20,12 @@ using pulse_gen::steps_t;
/// Specialized axis unit type for compile-time conformability testing. Like config::Unit /// Specialized axis unit type for compile-time conformability testing. Like config::Unit
/// this is done ensure unit quantities are not mixed between types, while also providing /// this is done ensure unit quantities are not mixed between types, while also providing
/// convenience methods to convert from physical units to AxisUnits directly at compile. /// convenience methods to convert from physical units to AxisUnits directly at compile
/// time. AxisUnits are just as efficient as non-checked pos_t and steps_t.
/// ///
/// Each axis unit type is separate for each axis, since the low-level count is not /// Each axis unit type is separate for each axis, since the low-level count is not
/// directly comparable across axes. Quantities are normally defined through the /// directly comparable across axes. Quantities are normally defined through the
/// literar operators. Types and base axes are prefixed with a single letter identifier /// literal operators. Types and base axes are prefixed with a single letter identifier
/// for the axis: P=pulley, S=selector, I=idler. /// for the axis: P=pulley, S=selector, I=idler.
/// ///
/// P_pos_t pulley_position = 10.0_P_mm; /// P_pos_t pulley_position = 10.0_P_mm;
@ -33,13 +34,38 @@ using pulse_gen::steps_t;
/// I_pos_t idler_position = 15.0_I_deg; /// I_pos_t idler_position = 15.0_I_deg;
/// pulley_position + idler_position; // compile time error /// pulley_position + idler_position; // compile time error
/// ///
/// modules::motion::Motion.PlanMove (and related functions) support AxisUnit natively. /// modules::motion::Motion.PlanMove (and related functions) support both physical and
/// AxisUnit natively. This is done by specifying the axis through the first template
/// parameter, which ensures related units are also conforming:
///
/// motion.PlanMoveTo<Pulley>(10.0_mm, 100._mm_s); // using physical units
/// motion.PlanMoveTo<Pulley>(10.0_P_mm, 100._P_mm_s); // using AxisUnit
///
/// Physical units are always represented with the largest floating point type, so they
/// should only be used at compile-time (constants, fixed-lenght moves).
///
/// If runtime manipulation is necessary, use AxisUnit should always be used instead.
/// Conversion from physical to AxisUnit can be done through unitToAxisUnit:
///
/// unitToAxisUnit<final_type>(physical_type)
///
/// Examples:
///
/// P_pos_t pulley_pos = unitToAxisUnit<P_pos_t>(10.0_mm);
/// P_speed_t pulley_speed = unitToAxisUnit<P_speed_t>(100.0_mm_s);
///
/// Conversion to pos_t or steps_t works the same using unitToSteps instead.
///
/// The low-level step count can be accessed when necessary through AxisUnit::v, which /// The low-level step count can be accessed when necessary through AxisUnit::v, which
/// should be avoided as it bypasses type checks. /// should be avoided as it bypasses type checks. AxisUnit can also be constructed by
/// providing a counter as the first initializer.
template <typename T, Axis A, config::UnitType U> template <typename T, Axis A, config::UnitType U>
struct AxisUnit { struct AxisUnit {
T v; T v;
static constexpr Axis axis = A;
static constexpr config::UnitType unit = U;
typedef T type_t; typedef T type_t;
typedef AxisUnit<T, A, U> self_t; typedef AxisUnit<T, A, U> self_t;
@ -50,58 +76,81 @@ struct AxisUnit {
constexpr self_t operator/(const self_t r) { return { v / r.v }; } constexpr self_t operator/(const self_t r) { return { v / r.v }; }
}; };
/// Axis type conversion table for template expansion
struct AxisScale {
unit::UnitBase base;
long double stepsPerUnit;
};
static constexpr AxisScale axisScale[config::NUM_AXIS] = {
{ config::pulleyLimits.base, config::pulley.stepsPerUnit },
{ config::selectorLimits.base, config::selector.stepsPerUnit },
{ config::idlerLimits.base, config::idler.stepsPerUnit },
};
/// Convert a Unit to AxisUnit
template <typename T, typename U>
static constexpr T unitToAxisUnit(U v) {
static_assert(T::unit == U::unit, "incorrect unit type conversion");
static_assert(U::base == axisScale[T::axis].base, "incorrect unit base conversion");
return { (typename T::type_t)(v.v * axisScale[T::axis].stepsPerUnit) };
}
/// Convert an Unit to a steps type (pos_t or steps_t)
template <typename AU, typename U>
static constexpr typename AU::type_t unitToSteps(U v) {
return unitToAxisUnit<AU>(v).v;
}
// Pulley
typedef AxisUnit<pos_t, Pulley, Lenght> P_pos_t; ///< Pulley position type (steps) typedef AxisUnit<pos_t, Pulley, Lenght> P_pos_t; ///< Pulley position type (steps)
typedef AxisUnit<steps_t, Pulley, Speed> P_speed_t; ///< Pulley speed type (steps/s) typedef AxisUnit<steps_t, Pulley, Speed> P_speed_t; ///< Pulley speed type (steps/s)
typedef AxisUnit<steps_t, Pulley, Accel> P_accel_t; ///< Pulley acceleration type (steps/s2) typedef AxisUnit<steps_t, Pulley, Accel> P_accel_t; ///< Pulley acceleration type (steps/s2)
/// Convert a Unit to AxisUnit
template <typename T, typename U>
static constexpr T unitToAxisUnit(const long double stepsPerUnit, U v) {
return { (typename T::type_t)(v.v * stepsPerUnit) };
}
static constexpr P_pos_t operator"" _P_mm(long double mm) { static constexpr P_pos_t operator"" _P_mm(long double mm) {
return { unitToAxisUnit<P_pos_t>(config::pulley.stepsPerUnit, config::U_mm { mm }) }; return { unitToAxisUnit<P_pos_t>(config::U_mm { mm }) };
} }
static constexpr P_speed_t operator"" _P_mm_s(long double mm_s) { static constexpr P_speed_t operator"" _P_mm_s(long double mm_s) {
return { unitToAxisUnit<P_speed_t>(config::pulley.stepsPerUnit, config::U_mm { mm_s }) }; return { unitToAxisUnit<P_speed_t>(config::U_mm_s { mm_s }) };
} }
static constexpr P_accel_t operator"" _P_mm_s2(long double mm_s2) { static constexpr P_accel_t operator"" _P_mm_s2(long double mm_s2) {
return { unitToAxisUnit<P_accel_t>(config::pulley.stepsPerUnit, config::U_mm_s2 { mm_s2 }) }; return { unitToAxisUnit<P_accel_t>(config::U_mm_s2 { mm_s2 }) };
} }
// Selector
typedef AxisUnit<pos_t, Selector, Lenght> S_pos_t; ///< Selector position type (steps) typedef AxisUnit<pos_t, Selector, Lenght> S_pos_t; ///< Selector position type (steps)
typedef AxisUnit<steps_t, Selector, Speed> S_speed_t; ///< Selector speed type (steps/s) typedef AxisUnit<steps_t, Selector, Speed> S_speed_t; ///< Selector speed type (steps/s)
typedef AxisUnit<steps_t, Selector, Accel> S_accel_t; ///< Selector acceleration type (steps/s2) typedef AxisUnit<steps_t, Selector, Accel> S_accel_t; ///< Selector acceleration type (steps/s2)
static constexpr S_pos_t operator"" _S_mm(long double mm) { static constexpr S_pos_t operator"" _S_mm(long double mm) {
return { unitToAxisUnit<S_pos_t>(config::selector.stepsPerUnit, config::U_mm { mm }) }; return { unitToAxisUnit<S_pos_t>(config::U_mm { mm }) };
} }
static constexpr S_speed_t operator"" _S_mm_s(long double mm_s) { static constexpr S_speed_t operator"" _S_mm_s(long double mm_s) {
return { unitToAxisUnit<S_speed_t>(config::selector.stepsPerUnit, config::U_mm_s { mm_s }) }; return { unitToAxisUnit<S_speed_t>(config::U_mm_s { mm_s }) };
} }
static constexpr S_accel_t operator"" _S_mm_s2(long double mm_s2) { static constexpr S_accel_t operator"" _S_mm_s2(long double mm_s2) {
return { unitToAxisUnit<S_accel_t>(config::selector.stepsPerUnit, config::U_mm_s2 { mm_s2 }) }; return { unitToAxisUnit<S_accel_t>(config::U_mm_s2 { mm_s2 }) };
} }
// Idler
typedef AxisUnit<pos_t, Idler, Lenght> I_pos_t; ///< Idler position type (steps) typedef AxisUnit<pos_t, Idler, Lenght> I_pos_t; ///< Idler position type (steps)
typedef AxisUnit<steps_t, Idler, Speed> I_speed_t; ///< Idler speed type (steps/s) typedef AxisUnit<steps_t, Idler, Speed> I_speed_t; ///< Idler speed type (steps/s)
typedef AxisUnit<steps_t, Idler, Accel> I_accel_t; ///< Idler acceleration type (steps/s2) typedef AxisUnit<steps_t, Idler, Accel> I_accel_t; ///< Idler acceleration type (steps/s2)
static constexpr I_pos_t operator"" _I_deg(long double deg) { static constexpr I_pos_t operator"" _I_deg(long double deg) {
return { unitToAxisUnit<I_pos_t>(config::idler.stepsPerUnit, config::U_deg { deg }) }; return { unitToAxisUnit<I_pos_t>(config::U_deg { deg }) };
} }
static constexpr I_speed_t operator"" _I_deg_s(long double deg_s) { static constexpr I_speed_t operator"" _I_deg_s(long double deg_s) {
return { unitToAxisUnit<I_speed_t>(config::idler.stepsPerUnit, config::U_deg_s { deg_s }) }; return { unitToAxisUnit<I_speed_t>(config::U_deg_s { deg_s }) };
} }
static constexpr I_accel_t operator"" _I_deg_s2(long double deg_s2) { static constexpr I_accel_t operator"" _I_deg_s2(long double deg_s2) {
return { unitToAxisUnit<I_accel_t>(config::idler.stepsPerUnit, config::U_deg_s2 { deg_s2 }) }; return { unitToAxisUnit<I_accel_t>(config::U_deg_s2 { deg_s2 }) };
} }
} // namespace motion } // namespace motion

View File

@ -28,8 +28,6 @@ struct AxisParams {
MotorParams params; MotorParams params;
MotorCurrents currents; MotorCurrents currents;
MotorMode mode; MotorMode mode;
long double stepsPerUnit;
config::UnitBase unitBase;
steps_t jerk; steps_t jerk;
steps_t accel; steps_t accel;
}; };
@ -39,12 +37,6 @@ static constexpr MotorMode DefaultMotorMode(const config::AxisConfig &axis) {
return axis.stealth ? MotorMode::Stealth : MotorMode::Normal; return axis.stealth ? MotorMode::Stealth : MotorMode::Normal;
} }
/// Convert an AxisUnit to a steps type (pos_t or steps_t)
template <typename AU, typename U>
static constexpr typename AU::type_t unitToSteps(const long double stepsPerUnit, U v) {
return unitToAxisUnit<AU>(stepsPerUnit, v).v;
}
/// Static axis configuration /// Static axis configuration
static constexpr AxisParams axisParams[NUM_AXIS] = { static constexpr AxisParams axisParams[NUM_AXIS] = {
// Pulley // Pulley
@ -53,10 +45,8 @@ static constexpr AxisParams axisParams[NUM_AXIS] = {
.params = { .idx = Pulley, .dirOn = config::pulley.dirOn, .csPin = PULLEY_CS_PIN, .stepPin = PULLEY_STEP_PIN, .sgPin = PULLEY_SG_PIN, .uSteps = config::pulley.uSteps }, .params = { .idx = Pulley, .dirOn = config::pulley.dirOn, .csPin = PULLEY_CS_PIN, .stepPin = PULLEY_STEP_PIN, .sgPin = PULLEY_SG_PIN, .uSteps = config::pulley.uSteps },
.currents = { .vSense = config::pulley.vSense, .iRun = config::pulley.iRun, .iHold = config::pulley.iHold }, .currents = { .vSense = config::pulley.vSense, .iRun = config::pulley.iRun, .iHold = config::pulley.iHold },
.mode = DefaultMotorMode(config::pulley), .mode = DefaultMotorMode(config::pulley),
.stepsPerUnit = config::pulley.stepsPerUnit, .jerk = unitToSteps<P_speed_t>(config::pulleyLimits.jerk),
.unitBase = config::PulleyLimits::base, .accel = unitToSteps<P_accel_t>(config::pulleyLimits.accel),
.jerk = unitToSteps<P_speed_t>(config::pulley.stepsPerUnit, config::pulleyLimits.jerk),
.accel = unitToSteps<P_accel_t>(config::pulley.stepsPerUnit, config::pulleyLimits.accel),
}, },
// Selector // Selector
{ {
@ -64,10 +54,8 @@ static constexpr AxisParams axisParams[NUM_AXIS] = {
.params = { .idx = Selector, .dirOn = config::selector.dirOn, .csPin = SELECTOR_CS_PIN, .stepPin = SELECTOR_STEP_PIN, .sgPin = SELECTOR_SG_PIN, .uSteps = config::selector.uSteps }, .params = { .idx = Selector, .dirOn = config::selector.dirOn, .csPin = SELECTOR_CS_PIN, .stepPin = SELECTOR_STEP_PIN, .sgPin = SELECTOR_SG_PIN, .uSteps = config::selector.uSteps },
.currents = { .vSense = config::selector.vSense, .iRun = config::selector.iRun, .iHold = config::selector.iHold }, .currents = { .vSense = config::selector.vSense, .iRun = config::selector.iRun, .iHold = config::selector.iHold },
.mode = DefaultMotorMode(config::selector), .mode = DefaultMotorMode(config::selector),
.stepsPerUnit = config::selector.stepsPerUnit, .jerk = unitToSteps<S_speed_t>(config::selectorLimits.jerk),
.unitBase = config::SelectorLimits::base, .accel = unitToSteps<S_accel_t>(config::selectorLimits.accel),
.jerk = unitToSteps<S_speed_t>(config::selector.stepsPerUnit, config::selectorLimits.jerk),
.accel = unitToSteps<S_accel_t>(config::selector.stepsPerUnit, config::selectorLimits.accel),
}, },
// Idler // Idler
{ {
@ -75,10 +63,8 @@ static constexpr AxisParams axisParams[NUM_AXIS] = {
.params = { .idx = Idler, .dirOn = config::idler.dirOn, .csPin = IDLER_CS_PIN, .stepPin = IDLER_STEP_PIN, .sgPin = IDLER_SG_PIN, .uSteps = config::idler.uSteps }, .params = { .idx = Idler, .dirOn = config::idler.dirOn, .csPin = IDLER_CS_PIN, .stepPin = IDLER_STEP_PIN, .sgPin = IDLER_SG_PIN, .uSteps = config::idler.uSteps },
.currents = { .vSense = config::idler.vSense, .iRun = config::idler.iRun, .iHold = config::idler.iHold }, .currents = { .vSense = config::idler.vSense, .iRun = config::idler.iRun, .iHold = config::idler.iHold },
.mode = DefaultMotorMode(config::idler), .mode = DefaultMotorMode(config::idler),
.stepsPerUnit = config::idler.stepsPerUnit, .jerk = unitToSteps<I_speed_t>(config::idlerLimits.jerk),
.unitBase = config::IdlerLimits::base, .accel = unitToSteps<I_accel_t>(config::idlerLimits.accel),
.jerk = unitToSteps<I_speed_t>(config::idler.stepsPerUnit, config::idlerLimits.jerk),
.accel = unitToSteps<I_accel_t>(config::idler.stepsPerUnit, config::idlerLimits.accel),
}, },
}; };
@ -133,10 +119,9 @@ public:
template <Axis A, config::UnitBase B> template <Axis A, config::UnitBase B>
constexpr void PlanMoveTo(config::Unit<long double, B, Lenght> pos, constexpr void PlanMoveTo(config::Unit<long double, B, Lenght> pos,
config::Unit<long double, B, Speed> feedrate) { config::Unit<long double, B, Speed> feedrate) {
static_assert(B == axisParams[A].unitBase, "incorrect unit base");
PlanMoveTo<A>( PlanMoveTo<A>(
unitToAxisUnit<AxisUnit<pos_t, A, Lenght>>(axisParams[A].stepsPerUnit, pos), unitToAxisUnit<AxisUnit<pos_t, A, Lenght>>(pos),
unitToAxisUnit<AxisUnit<steps_t, A, Speed>>(axisParams[A].stepsPerUnit, feedrate)); unitToAxisUnit<AxisUnit<steps_t, A, Speed>>(feedrate));
} }
/// Enqueue a single axis move in steps starting and ending at zero speed with maximum /// Enqueue a single axis move in steps starting and ending at zero speed with maximum
@ -162,10 +147,9 @@ public:
template <Axis A, config::UnitBase B> template <Axis A, config::UnitBase B>
constexpr void PlanMove(config::Unit<long double, B, Lenght> delta, constexpr void PlanMove(config::Unit<long double, B, Lenght> delta,
config::Unit<long double, B, Speed> feedrate) { config::Unit<long double, B, Speed> feedrate) {
static_assert(B == axisParams[A].unitBase, "incorrect unit base");
PlanMove<A>( PlanMove<A>(
unitToAxisUnit<AxisUnit<pos_t, A, Lenght>>(axisParams[A].stepsPerUnit, delta), unitToAxisUnit<AxisUnit<pos_t, A, Lenght>>(delta),
unitToAxisUnit<AxisUnit<steps_t, A, Speed>>(axisParams[A].stepsPerUnit, feedrate)); unitToAxisUnit<AxisUnit<steps_t, A, Speed>>(feedrate));
} }
/// @returns head position of an axis (last enqueued position) /// @returns head position of an axis (last enqueued position)
@ -264,7 +248,7 @@ private:
}; };
/// ISR stepping routine /// ISR stepping routine
extern void ISR(); //extern void ISR();
extern Motion motion; extern Motion motion;

View File

@ -41,6 +41,9 @@ template <typename T, UnitBase B, UnitType U>
struct Unit { struct Unit {
T v; T v;
static constexpr UnitBase base = B;
static constexpr UnitType unit = U;
typedef T type_t; typedef T type_t;
typedef Unit<T, B, U> self_t; typedef Unit<T, B, U> self_t;