132 lines
5.0 KiB
C++
132 lines
5.0 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "fgc/DumpParser.h"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
using namespace fgc;
|
|
|
|
namespace {
|
|
|
|
// A realistic two-axis firmware DUMP block (the on-wire format from
|
|
// firmware/src/motor.cpp printDump): header, then per axis a state line and a
|
|
// TMC register line, bracketed by DUMP BEGIN / DUMP END.
|
|
const char* kDump =
|
|
"DUMP BEGIN build=11dd3ce-dirty uptime=11740067 mcusr=0x01 free_ram=1852\n"
|
|
"DUMP Y state=3 hsub=10 enabled=1 lim_neg=-82919 lim_pos=98687 hold_target=-90 "
|
|
"speed=50000 eeprom_restored=0 has_encoder=1\n"
|
|
"DUMP Y TMC GCONF=0x0000000C GSTAT=0x00000000 IOIN=0x30000008 TSTEP=0x000FFFFF "
|
|
"RAMPMODE=0x00000000 XACTUAL=0xFFFFFFA6 VACTUAL=0x00000000 XTARGET=0xFFFFFFA6 "
|
|
"SW_MODE=0x000008A0 RAMP_STAT=0x00001680 X_ENC=0xFFFFFF7F ENC_STATUS=0x00000002 "
|
|
"CHOPCONF=0x00410043 DRV_STATUS=0x80084000 PWM_SCALE=0x00000011 PWM_AUTO=0x003F0039\n"
|
|
"DUMP P state=3 hsub=10 enabled=1 lim_neg=495183 lim_pos=1069042 hold_target=500183 "
|
|
"speed=150000 eeprom_restored=0 has_encoder=1\n"
|
|
"DUMP P TMC GCONF=0x0000000C GSTAT=0x00000000 IOIN=0x30000008 TSTEP=0x000FFFFF "
|
|
"RAMPMODE=0x00000000 XACTUAL=0x0007A1C8 VACTUAL=0x00000000 XTARGET=0x0007A1C8 "
|
|
"SW_MODE=0x000008A3 RAMP_STAT=0x00001688 X_ENC=0x0007A231 ENC_STATUS=0x00000002 "
|
|
"CHOPCONF=0x00410043 DRV_STATUS=0x80084000 PWM_SCALE=0x00010011 PWM_AUTO=0x0021003C\n"
|
|
"DUMP END\n";
|
|
|
|
bool has(const std::vector<std::string>& v, const std::string& s) {
|
|
return std::find(v.begin(), v.end(), s) != v.end();
|
|
}
|
|
|
|
const DumpAxis* axis(const DumpData& d, char a) {
|
|
for (const auto& ax : d.axes)
|
|
if (ax.axis == a) return &ax;
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("parseDump decodes the header") {
|
|
DumpData d = parseDump(kDump);
|
|
REQUIRE(d.valid);
|
|
CHECK(d.build == "11dd3ce-dirty");
|
|
CHECK(d.uptime_ms == 11740067);
|
|
CHECK(d.mcusr == 0x01u);
|
|
CHECK(d.free_ram == 1852);
|
|
CHECK(has(d.reset_flags, "PORF (power-on)"));
|
|
REQUIRE(d.axes.size() == 2);
|
|
}
|
|
|
|
TEST_CASE("parseDump decodes the per-axis state line") {
|
|
DumpData d = parseDump(kDump);
|
|
const DumpAxis* y = axis(d, 'Y');
|
|
REQUIRE(y != nullptr);
|
|
CHECK(y->state_name == "READY"); // state=3
|
|
CHECK(y->enabled);
|
|
CHECK(y->has_encoder);
|
|
CHECK_FALSE(y->eeprom_restored);
|
|
CHECK(y->lim_neg == -82919);
|
|
CHECK(y->lim_pos == 98687);
|
|
CHECK(y->hold_target == -90);
|
|
CHECK(y->speed == 50000);
|
|
CHECK(y->hsub == 10);
|
|
|
|
const DumpAxis* p = axis(d, 'P');
|
|
REQUIRE(p != nullptr);
|
|
CHECK(p->lim_neg == 495183);
|
|
CHECK(p->lim_pos == 1069042);
|
|
}
|
|
|
|
TEST_CASE("parseDump keeps all 16 TMC registers (regression: TMC-line whitespace)") {
|
|
// The "DUMP Y TMC ..." line was once misclassified due to a leading-space
|
|
// slice, dropping every register. Guard that all of them survive.
|
|
DumpData d = parseDump(kDump);
|
|
const DumpAxis* y = axis(d, 'Y');
|
|
REQUIRE(y != nullptr);
|
|
for (const char* r : {"GCONF", "GSTAT", "IOIN", "TSTEP", "RAMPMODE", "XACTUAL",
|
|
"VACTUAL", "XTARGET", "SW_MODE", "RAMP_STAT", "X_ENC",
|
|
"ENC_STATUS", "CHOPCONF", "DRV_STATUS", "PWM_SCALE", "PWM_AUTO"}) {
|
|
CHECK_MESSAGE(y->regs.count(r) == 1, "missing register ", r);
|
|
}
|
|
CHECK(y->regs.at("RAMP_STAT") == "0x00001680");
|
|
CHECK(y->regs.at("DRV_STATUS") == "0x80084000");
|
|
}
|
|
|
|
TEST_CASE("parseDump decodes status-register bit flags") {
|
|
DumpData d = parseDump(kDump);
|
|
const DumpAxis* y = axis(d, 'Y');
|
|
REQUIRE(y != nullptr);
|
|
|
|
// DRV_STATUS = 0x80084000 -> bit31 stst, bit14 stealth; CS_ACTUAL=(>>16)&0x1F=8.
|
|
CHECK(y->drv_status == 0x80084000u);
|
|
CHECK(has(y->drv_flags, "stst"));
|
|
CHECK(has(y->drv_flags, "stealth"));
|
|
CHECK(y->cs_actual == 8);
|
|
CHECK(y->sg_result == 0);
|
|
|
|
// GSTAT = 0 -> no flags (placeholder "-").
|
|
CHECK(has(y->gstat_flags, "-"));
|
|
|
|
// RAMP_STAT = 0x1680 -> bit7 event_pos_reached, bit9 position_reached, bit10 vzero.
|
|
CHECK(y->ramp_stat == 0x00001680u);
|
|
CHECK(has(y->ramp_flags, "event_pos_reached"));
|
|
CHECK(has(y->ramp_flags, "position_reached"));
|
|
CHECK(has(y->ramp_flags, "vzero"));
|
|
}
|
|
|
|
TEST_CASE("parseDump tolerates a corrupted/merged DUMP END (best-effort)") {
|
|
// The host detects DUMP END anywhere in a line; a clean header still parses
|
|
// even when later content is mangled — but a block with no BEGIN/END is invalid.
|
|
CHECK_FALSE(parseDump("").valid);
|
|
CHECK_FALSE(parseDump("garbage with no markers\n").valid);
|
|
CHECK_FALSE(parseDump("DUMP Y state=3\nno end marker\n").valid);
|
|
}
|
|
|
|
TEST_CASE("formatDump renders valid and invalid dumps") {
|
|
auto bad = formatDump(DumpData{});
|
|
REQUIRE(bad.size() == 1);
|
|
CHECK(bad[0].find("no firmware dump") != std::string::npos);
|
|
|
|
auto good = formatDump(parseDump(kDump));
|
|
std::string joined;
|
|
for (const auto& l : good) joined += l + "\n";
|
|
CHECK(joined.find("build=11dd3ce-dirty") != std::string::npos);
|
|
CHECK(joined.find("[Y]") != std::string::npos);
|
|
CHECK(joined.find("[P]") != std::string::npos);
|
|
CHECK(joined.find("RAMP_STAT") != std::string::npos);
|
|
}
|