Skip to content

Implement STSPIN motor controller and refactor motor service#3715

Open
williamckha wants to merge 10 commits into
masterfrom
tbots/motor
Open

Implement STSPIN motor controller and refactor motor service#3715
williamckha wants to merge 10 commits into
masterfrom
tbots/motor

Conversation

@williamckha
Copy link
Copy Markdown
Member

Description

Testing Done

Resolved Issues

Resolves #3711

Length Justification and Key Files to Review

Review Checklist

It is the reviewers responsibility to also make sure every item here has been covered

  • Function & Class comments: All function definitions (usually in the .h file) should have a javadoc style comment at the start of them. For examples, see the functions defined in thunderbots/software/geom. Similarly, all classes should have an associated Javadoc comment explaining the purpose of the class.
  • Remove all commented out code
  • Remove extra print statements: for example, those just used for testing
  • Resolve all TODO's: All TODO (or similar) statements should either be completed or associated with a github issue

@williamckha williamckha linked an issue May 18, 2026 that may be closed by this pull request
1 task
Comment on lines +19 to +20
void spiTransfer(int fd, uint8_t const* tx, uint8_t const* rx, unsigned len,
uint32_t spi_speed);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor these SPI transfer functions to take std::array as params instead of raw pointers

Comment on lines +35 to +39
using OutgoingFrame =
std::variant<NoOpFrame, SetResponseTypeFrame, SetTargetSpeedFrame,
SetTargetTorqueFrame, SetPidTorqueKpKiFrame, SetPidFluxKpKiFrame,
SetPidSpeedKpKiFrame, SetSpeedFeedForwardKaKvFrame,
SetSpeedFeedForwardKsFrame>;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using std::variant and visitor in processTx, consider turning processTx into a template method with frame type as a template param, and use template specialization to select implementation based on frame type

<< "THUNDERLOOP: Network Service initialized! Next initializing Power Service";

power_service_ = std::make_unique<PowerService>();
// power_service_ = std::make_unique<PowerService>();
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reenable power service before this is merged into master

Comment thread src/software/constants.h

const std::string SSL_VISION_ADDRESS = "224.5.23.2";
static constexpr unsigned int SSL_VISION_PORT = 10020;
static constexpr unsigned int SSL_VISION_PORT = 10006;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the correct default SSL vision port

Comment on lines +9 to +17
constexpr std::array<MotorIndex, 4> driveMotors()
{
return {
MotorIndex::FRONT_LEFT,
MotorIndex::FRONT_RIGHT,
MotorIndex::BACK_LEFT,
MotorIndex::BACK_RIGHT,
};
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to make this a global variable instead of a function returning a new array every time. This isn't great because e.g. driveMotors().begin() and driveMotors().end() will return iterators to different arrays

Comment on lines +104 to +116
/*********** STSPIN Faults ************/
NO_FAULT = 15;
DURATION = 16;
OVER_VOLT = 17;
UNDER_VOLT = 18;
OVER_TEMP = 19;
START_UP = 20;
SPEED_FDBK = 21;
OVER_CURR = 22;
SW_ERROR = 23;
SAMPLE_FAULT = 24;
OVERCURR_SW = 25;
DP_FAULT = 26;
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kind of a hacky solution just appending these new fault types to our MotorFault enum. It would be better if we had separate enums for Trinamic vs. STSPIN faults

Comment on lines +6 to +21
bool Gpio::pollValue(GpioState state, std::chrono::milliseconds timeout_ms)
{
const auto start_time = std::chrono::system_clock::now();
while (getValue() != state)
{
std::this_thread::sleep_for(std::chrono::microseconds(100));

const auto current_time = std::chrono::system_clock::now();
const auto elapsed_time = current_time - start_time;
if (elapsed_time > timeout_ms)
{
return false;
}
}
return true;
}
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should use steady_clock instead of system_clock because system_clock is not always monotonically increasing

Comment on lines +21 to +22
logWorker->addSink(std::make_unique<PlotJugglerSink>("tbotswifi5"),
&PlotJugglerSink::sendToPlotJuggler);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we could change the network interface used for the PlotJugglerSink at runtime and set it to whatever interface we've configured in the robot_config.toml file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #3718, can you complete the accept criteria? I am not 100% sure I understand what you want here. Am I right to imagine that you want the sink to be added dynamically at startup after the contents of the toml file are transferred over the network to the device running thunderscope/logger? Or, is this logger running on thunderloop itself and thus the contents of the toml do not need to be forwarded

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NetworkLogger runs on the robot, i.e. as part of thunderloop. I don't think you can add sinks after glog is initialized (which happens before the toml config is loaded), but we can add a method to the plotjuggler sink that lets us reconfigure the network interface used + reopen socket, and then assuming we hold a reference to the plotjuggler sink, call this method after we've fetched the network interface from the toml config.

Andrewyx
Andrewyx previously approved these changes May 18, 2026
Copy link
Copy Markdown
Contributor

@Andrewyx Andrewyx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments on the scope of some parts. We can either merge this one in and then refactor over it through subtasks, or remove the unrelated portions and have subtasks begin cleanly.


// Constants for the Raspberry Pi

constexpr int SPI_CS_DRIVER_TO_CONTROLLER_MUX_0_GPIO = 16;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comments to these

Comment on lines +58 to +65
static constexpr int RESET_GPIO_PIN = 12;

static constexpr int SPEED_PID_PROPORTIONAL_GAIN = 700;
static constexpr int SPEED_PID_INTEGRAL_GAIN = 30;

static constexpr int MAX_SPEED_FEED_FORWARD_STATIC_GAIN = 750;
static constexpr int MIN_SPEED_FEED_FORWARD_STATIC_GAIN = 300;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: docs

Comment on lines +88 to +167
std::ostringstream oss;
oss << "======= Faults For Motor " << motor << "=======\n";

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::DURATION))
{
oss << "DURATION: FOC rate too high\n";
motor_faults.faults.insert(TbotsProto::MotorFault::DURATION);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::OVER_VOLT))
{
oss << "OVER_VOLT: Over voltage\n";
motor_faults.faults.insert(TbotsProto::MotorFault::OVER_VOLT);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::UNDER_VOLT))
{
oss << "UNDER_VOLT: Under voltage\n";
motor_faults.faults.insert(TbotsProto::MotorFault::UNDER_VOLT);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::OVER_TEMP))
{
oss << "OVER_TEMP: Over temperature\n";
motor_faults.faults.insert(TbotsProto::MotorFault::OVER_TEMP);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::START_UP))
{
oss << "START_UP: Start up failed\n";
motor_faults.faults.insert(TbotsProto::MotorFault::START_UP);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::SPEED_FDBK))
{
oss << "SPEED_FDBK: Speed feedback fault\n";
motor_faults.faults.insert(TbotsProto::MotorFault::SPEED_FDBK);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::OVER_CURR))
{
oss << "OVER_CURR: Over current\n";
motor_faults.faults.insert(TbotsProto::MotorFault::OVER_CURR);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::SW_ERROR))
{
oss << "SW_ERROR: Software error\n";
motor_faults.faults.insert(TbotsProto::MotorFault::SW_ERROR);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::SAMPLE_FAULT))
{
oss << "SAMPLE_FAULT: Sample fault for testing purposes\n";
motor_faults.faults.insert(TbotsProto::MotorFault::SAMPLE_FAULT);
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::OVERCURR_SW))
{
oss << "OVERCURR_SW: Software over current\n";
motor_faults.faults.insert(TbotsProto::MotorFault::OVERCURR_SW);
motor_faults.drive_enabled = false;
}

if (fault_flags & static_cast<uint16_t>(StSpinFaultCode::DP_FAULT))
{
oss << "DP_FAULT: Driver protection fault\n";
motor_faults.faults.insert(TbotsProto::MotorFault::DP_FAULT);
motor_faults.drive_enabled = false;
}

LOG(WARNING) << oss.str();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving this to a helper and also making the stream object persistent so we do not need to continuously recreate it (it is notoriously expensive). We could also consider another approach to formating this string as using streams in general is less than ideal.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably just use string::append

Comment on lines +191 to +195
const Vector local_velocity(target_euclidean_velocity[1],
-target_euclidean_velocity[0]);

if (local_velocity.length() <= 0.01)
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: magic numbers

Comment on lines +66 to +83
struct SetPidTorqueKpKiFrame
{
int16_t kp;
int16_t ki;
};

struct SetPidFluxKpKiFrame
{
int16_t kp;
int16_t ki;
};

struct SetPidSpeedKpKiFrame
{
int16_t kp;
int16_t ki;
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need so many structs of the same underlying structure?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type of frame changes how we populate the transmit buffer (mainly opcode at the start of the frame)

Comment on lines 147 to 151
def stop_test(delay):
time.sleep(delay)
if self.thunderscope:
self.thunderscope.close()
# We no longer close thunderscope here, because a test might call run_test multiple times.
# Thunderscope will be closed when the fixture is torn down.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark with todo, this function does nothing currently. We should close thunderscope after a while once field test completes or fails

Comment on lines +366 to +372
parser.add_argument(
"--no_visualization",
action="store_true",
default=False,
help="Disables the Thunderscope GUI",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feature is probably unnecessary, since we almost always want a GUI

id: tactic,
},
yellow_tactics=None,
def execute_test():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes are to fix field tests for the new gc changes for 2026. We should extract this to a separate PR. TODO #3717

Comment on lines +21 to +22
logWorker->addSink(std::make_unique<PlotJugglerSink>("tbotswifi5"),
&PlotJugglerSink::sendToPlotJuggler);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #3718, can you complete the accept criteria? I am not 100% sure I understand what you want here. Am I right to imagine that you want the sink to be added dynamically at startup after the contents of the toml file are transferred over the network to the device running thunderscope/logger? Or, is this logger running on thunderloop itself and thus the contents of the toml do not need to be forwarded

Comment on lines +34 to +38
# State for drag-to-orient movement
self.is_dragging_to_orient = False
self.target_point = None
self.current_orientation = -math.pi / 2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract this work to #3719

@williamckha williamckha changed the base branch from tbots/robot_constants to master May 20, 2026 00:51
@williamckha williamckha dismissed Andrewyx’s stale review May 20, 2026 00:51

The base branch was changed.

Copy link
Copy Markdown
Contributor

@GrayHoang GrayHoang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments for me to sort each file change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation/Testing (MD)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

robot constants and robot control

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

robot constants and robot control

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

robot constants and control

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

robot constants and control

Comment thread src/software/constants.h

const std::string SSL_VISION_ADDRESS = "224.5.23.2";
static constexpr unsigned int SSL_VISION_PORT = 10020;
static constexpr unsigned int SSL_VISION_PORT = 10006;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

debugging

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

robotconstants

Comment thread src/.bazelrc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constants

Comment thread src/MODULE.bazel
Comment thread src/extlibs/cppcrc.BUILD
williamckha and others added 2 commits May 22, 2026 18:30
…into tbots/motor

# Conflicts:
#	src/software/embedded/services/motor.cpp
#	src/software/embedded/services/motor.h
#	src/software/embedded/services/robot_auto_test.cpp
#	src/software/embedded/thunderloop.cpp
#	src/software/simulated_tests/simulated_er_force_sim_test_fixture.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MDv6 Thunderloop Integration

4 participants