fwt_software/tests/test_tmccurrent.cpp

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);
}