Implement STSPIN motor controller and refactor motor service#3715
Implement STSPIN motor controller and refactor motor service#3715williamckha wants to merge 10 commits into
Conversation
| void spiTransfer(int fd, uint8_t const* tx, uint8_t const* rx, unsigned len, | ||
| uint32_t spi_speed); |
There was a problem hiding this comment.
Refactor these SPI transfer functions to take std::array as params instead of raw pointers
| using OutgoingFrame = | ||
| std::variant<NoOpFrame, SetResponseTypeFrame, SetTargetSpeedFrame, | ||
| SetTargetTorqueFrame, SetPidTorqueKpKiFrame, SetPidFluxKpKiFrame, | ||
| SetPidSpeedKpKiFrame, SetSpeedFeedForwardKaKvFrame, | ||
| SetSpeedFeedForwardKsFrame>; |
There was a problem hiding this comment.
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>(); |
There was a problem hiding this comment.
Reenable power service before this is merged into master
|
|
||
| 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; |
There was a problem hiding this comment.
This is the correct default SSL vision port
| constexpr std::array<MotorIndex, 4> driveMotors() | ||
| { | ||
| return { | ||
| MotorIndex::FRONT_LEFT, | ||
| MotorIndex::FRONT_RIGHT, | ||
| MotorIndex::BACK_LEFT, | ||
| MotorIndex::BACK_RIGHT, | ||
| }; | ||
| } |
There was a problem hiding this comment.
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
| /*********** 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; |
There was a problem hiding this comment.
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
| 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; | ||
| } |
There was a problem hiding this comment.
Should use steady_clock instead of system_clock because system_clock is not always monotonically increasing
| logWorker->addSink(std::make_unique<PlotJugglerSink>("tbotswifi5"), | ||
| &PlotJugglerSink::sendToPlotJuggler); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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; |
| 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; | ||
|
|
| 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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
We can probably just use string::append
| const Vector local_velocity(target_euclidean_velocity[1], | ||
| -target_euclidean_velocity[0]); | ||
|
|
||
| if (local_velocity.length() <= 0.01) | ||
| { |
| struct SetPidTorqueKpKiFrame | ||
| { | ||
| int16_t kp; | ||
| int16_t ki; | ||
| }; | ||
|
|
||
| struct SetPidFluxKpKiFrame | ||
| { | ||
| int16_t kp; | ||
| int16_t ki; | ||
| }; | ||
|
|
||
| struct SetPidSpeedKpKiFrame | ||
| { | ||
| int16_t kp; | ||
| int16_t ki; | ||
| }; | ||
|
|
There was a problem hiding this comment.
Why do we need so many structs of the same underlying structure?
There was a problem hiding this comment.
The type of frame changes how we populate the transmit buffer (mainly opcode at the start of the frame)
| 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. | ||
|
|
There was a problem hiding this comment.
Mark with todo, this function does nothing currently. We should close thunderscope after a while once field test completes or fails
| parser.add_argument( | ||
| "--no_visualization", | ||
| action="store_true", | ||
| default=False, | ||
| help="Disables the Thunderscope GUI", | ||
| ) | ||
|
|
There was a problem hiding this comment.
this feature is probably unnecessary, since we almost always want a GUI
| id: tactic, | ||
| }, | ||
| yellow_tactics=None, | ||
| def execute_test(): |
There was a problem hiding this comment.
These changes are to fix field tests for the new gc changes for 2026. We should extract this to a separate PR. TODO #3717
| logWorker->addSink(std::make_unique<PlotJugglerSink>("tbotswifi5"), | ||
| &PlotJugglerSink::sendToPlotJuggler); |
There was a problem hiding this comment.
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
| # State for drag-to-orient movement | ||
| self.is_dragging_to_orient = False | ||
| self.target_point = None | ||
| self.current_orientation = -math.pi / 2 | ||
|
|
GrayHoang
left a comment
There was a problem hiding this comment.
comments for me to sort each file change
There was a problem hiding this comment.
Documentation/Testing (MD)
There was a problem hiding this comment.
robot constants and robot control
There was a problem hiding this comment.
robot constants and robot control
There was a problem hiding this comment.
robot constants and control
There was a problem hiding this comment.
robot constants and control
|
|
||
| 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; |
…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
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
.hfile) should have a javadoc style comment at the start of them. For examples, see the functions defined inthunderbots/software/geom. Similarly, all classes should have an associated Javadoc comment explaining the purpose of the class.TODO(or similar) statements should either be completed or associated with a github issue