106 lines
3.9 KiB
C++
106 lines
3.9 KiB
C++
#include "doctest/doctest.h"
|
|
#include "fgc/TmcCurrent.h"
|
|
|
|
#include <cmath>
|
|
|
|
using namespace fgc;
|
|
|
|
namespace {
|
|
// The IOTA tower's driver board.
|
|
constexpr double kR = 0.075;
|
|
} // namespace
|
|
|
|
TEST_CASE("the RMS current formula matches the datasheet") {
|
|
// Full scale: GLOBALSCALER 256 (encoded 0), IRUN 31.
|
|
CHECK(tmcRmsCurrentMa(0, 31, kR) == doctest::Approx(2419.0).epsilon(0.001));
|
|
// The fleet's setting.
|
|
CHECK(tmcRmsCurrentMa(80, 31, kR) == doctest::Approx(756.0).epsilon(0.002));
|
|
// A non-positive sense resistor is a config error, not a division to attempt.
|
|
CHECK(tmcRmsCurrentMa(80, 31, 0.0) == 0.0);
|
|
}
|
|
|
|
TEST_CASE("the fleet's run current lands on the values the firmware already ships") {
|
|
// 80 is what firmware src/config.h carries for yaw, arrived at independently.
|
|
// If this test fails, either the formula or that constant has drifted.
|
|
auto p = planTmcCurrent(kR, 756.0, 200.0);
|
|
REQUIRE(p.ok);
|
|
CHECK(p.global_scaler == 80);
|
|
CHECK(p.run_irun == 31);
|
|
CHECK(p.homing_irun == 7);
|
|
CHECK(p.run_ma == doctest::Approx(756.0).epsilon(0.002));
|
|
CHECK(p.homing_ma == doctest::Approx(189.0).epsilon(0.01));
|
|
CHECK(p.warnings.empty());
|
|
}
|
|
|
|
TEST_CASE("an achieved current is recomputed from the registers, never echoed back") {
|
|
// The log must not flatter the result: what is reported has to be what the
|
|
// chosen registers deliver.
|
|
for (double r : {0.05, 0.075, 0.11}) {
|
|
for (double run : {300.0, 756.0, 1500.0}) {
|
|
auto p = planTmcCurrent(r, run, run / 4.0);
|
|
REQUIRE(p.ok);
|
|
CHECK(p.run_ma == doctest::Approx(tmcRmsCurrentMa(p.global_scaler, p.run_irun, r)));
|
|
CHECK(p.homing_ma ==
|
|
doctest::Approx(tmcRmsCurrentMa(p.global_scaler, p.homing_irun, r)));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("GLOBALSCALER never lands in the band the datasheet forbids") {
|
|
// 1..31 is disallowed, and 0 means 256 (full scale) - emitting either would
|
|
// mean silently maximum current on a rig whose endstop yields to full torque.
|
|
for (double r : {0.05, 0.075, 0.11, 0.22}) {
|
|
for (double run = 10.0; run <= 3000.0; run += 10.0) {
|
|
auto p = planTmcCurrent(r, run, run / 2.0);
|
|
REQUIRE(p.ok);
|
|
CHECK(p.global_scaler >= 32);
|
|
CHECK(p.global_scaler <= 255);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("a run current the driver cannot reach is clamped and named") {
|
|
auto p = planTmcCurrent(kR, 5000.0, 200.0);
|
|
REQUIRE(p.ok);
|
|
CHECK(p.global_scaler == 255);
|
|
CHECK(p.run_ma < 2500.0);
|
|
REQUIRE(p.warnings.size() >= 1);
|
|
CHECK(p.warnings[0].find("5000") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("a homing current above the run current is refused, not obeyed") {
|
|
auto p = planTmcCurrent(kR, 756.0, 900.0);
|
|
REQUIRE(p.ok);
|
|
CHECK(p.homing_irun == p.run_irun);
|
|
REQUIRE(p.warnings.size() >= 1);
|
|
CHECK(p.warnings[0].find("above the run current") != std::string::npos);
|
|
}
|
|
|
|
TEST_CASE("a homing current below the smallest expressible step says so") {
|
|
auto p = planTmcCurrent(kR, 756.0, 5.0);
|
|
REQUIRE(p.ok);
|
|
CHECK(p.homing_irun == 0);
|
|
CHECK(p.homing_ma > 5.0); // stronger than asked - the direction worth naming
|
|
bool named = false;
|
|
for (const auto& w : p.warnings)
|
|
if (w.find("smallest step") != std::string::npos) named = true;
|
|
CHECK(named);
|
|
}
|
|
|
|
TEST_CASE("the homing current never exceeds the run current after rounding") {
|
|
for (double r : {0.05, 0.075, 0.11}) {
|
|
for (double run = 50.0; run <= 2000.0; run += 50.0) {
|
|
auto p = planTmcCurrent(r, run, run * 0.99);
|
|
REQUIRE(p.ok);
|
|
CHECK(p.homing_irun <= p.run_irun);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("non-positive inputs are rejected rather than computed") {
|
|
CHECK_FALSE(planTmcCurrent(0.0, 756.0, 200.0).ok);
|
|
CHECK_FALSE(planTmcCurrent(kR, 0.0, 200.0).ok);
|
|
CHECK_FALSE(planTmcCurrent(kR, 756.0, 0.0).ok);
|
|
CHECK(planTmcCurrent(0.0, 756.0, 200.0).warnings.size() == 1);
|
|
}
|