Repository navigation
Add tool board firmware that speaks the MKS drive frames - #32
Conversation
Replace the placeholder with the five-joint arm exported from full-arm-smaller.SLDASM. The tools in scripts/cad regenerate the URDF and meshes after CAD changes. demo_mode:=true sweeps each joint in turn and reports a pass/fail row per joint on /diagnostics. Supersedes #7.
Drive the five joints with an Xbox controller through placeholder MKS SERVO42D/57D CAN drives, simulated until the real drives are connected. On Windows, a Python-only XInput bridge sends the controller state to the new wslg-teleop Compose service over UDP. The RViz camera now follows the tool so the end of the arm stays in view.
# Conflicts: # waybionic_bringup/launch/ground_station.launch.py
# Conflicts: # waybionic_bringup/CMakeLists.txt
demo_mode:=True or 1 started joint_state_publisher_gui next to the demo, because the launch compared the raw text with 'true'. And/Not substitutions parse booleans the same way IfCondition does. The joint demo test now uses True and fails without this change. joint_demo rejects a speed_deg_s of zero or less at startup instead of never finishing a move, and receives use_sim_time like the other nodes. The CAD script's help now says which export --check-moves compares.
# Conflicts: # waybionic_bringup/launch/ground_station.launch.py
follow_camera:=false stopped the camera follower, so RViz lost the view_focus frame it orbits. The follower now always runs and holds its default focus when follow is false, and a launch test checks the frame. teleop and joy_source use And/Or/Equals substitutions, so teleop:=True works like IfCondition; the teleop test now uses True. The new nodes receive use_sim_time, and the UDP receiver reads at most 100 packets per poll so a flood cannot starve its other callbacks.
The arm is a base yaw, three parallel pitch joints and a roll through the tool tip, so kinematics.py solves any tip position and tool tilt in closed form from the URDF. The new Cartesian group (Y) moves the tip along straight lines in base_link with the tilt fixed; LB keeps only the strongest axis. At a joint speed limit or the workspace edge the whole step shrinks, so the tip stops on the line, and the roll turns against the yaw to keep the tool's heading. Each drive now gets the next setpoint one period ahead and the speed that reaches it in exactly one period, measured from its encoder, so all drives arrive together. acc is 255 because a gentle drive ramp adds the same rpm/s to every drive and the one with the largest change falls behind. In simulation a sideways cut at 30:1 gearing stays within 7-17 um of the line, compared with up to 186 um with the old 1.5x speed rule. At the 1:1 placeholder gearing it stays within about 0.13 mm, one encoder count at the tip.
D-pad left/right changes the tool pitch while the tip stays in place, so the shoulder, elbow and wrist move around a fixed point. The tilt uses the same closed-form step as straight moves, so a joint limit stops it with the tip still in place. It runs at up to 30 deg/s, scaled by the speed level.
- Start is refused while a tilt button or A is held, like the sticks. - Homing clears the Cartesian rates, so releasing A can't resume the last move. - Cartesian roll ramps with the acceleration limit and scales with the accepted step. - The drives' setpoint one period ahead stays within the URDF joint limits, which the drive node now reads from robot_description. - If one drive would pass max_rpm, every drive slows by the same factor, so they still arrive together. - The roll compensation is documented as stopping the tool spinning about its own axis; that keeps a blade's heading only when the tool points straight down. - The Cartesian launch test is registered where it won't conflict with #22.
Reviewed in #24, which was merged into its stacked base after #23 had already been squash-merged into main, so it never reached main. This is the same change, applied to main. Drive the five joints with an Xbox controller through placeholder MKS SERVO42D/57D CAN drives, simulated until the real drives are connected. On Windows, a Python-only XInput bridge sends the controller state to the new wslg-teleop Compose service over UDP. The RViz camera follows the tool so the end of the arm stays in view; follow_camera:=false keeps a fixed view. teleop and joy_source use And/Or/Equals substitutions, so teleop:=True works like IfCondition. The new nodes receive use_sim_time, and the UDP receiver reads at most 100 packets per poll so a flood cannot starve its other callbacks.
The drive node can now send its frames to real MKS SERVO42D/57D drives through any python-can interface, chosen with the drive_interface and drive_channel launch arguments. The simulation stays the default. Because the encoders count from power-on, nothing is published or driven until the arm is zeroed (92h) through ~/zero. Simulated drives are zeroed at start-up. A drive that stops answering stops every drive and must be zeroed again. Stale commands hold the last target, and the drives are stopped when the node exits. mks_drive_sim answers on a CAN interface like the drives do. A new CI job runs the end-to-end test over vcan0. UDP multicast hands every frame back to its sender, and MKS replies reuse the command's CAN ID, so the bus drops its own echoes.
The biomedical tool's four NEMA 8 steppers (A4988 drivers) answer on CAN IDs 7 to 10 like MKS SERVO drives: read encoder, mode, replies, zero, heartbeat, enable and absolute moves. The ground station can drive them with the same host code as the arm. The sketch targets the UNO R4 WiFi with a CNC Shield V3 layout. Its travel limits are placeholders until the Biomedical team's limits are copied in. The frame code is plain C++, and a teleop test compiles it to check it against mks_can.py. A CI job compiles the sketch with arduino-cli.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 18 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Stop commands ignore their requested acceleration, and heartbeat safety behavior lacks automated coverage.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds UNO R4 firmware allowing four biomedical tool-board steppers to emulate MKS CAN drives.
Changes:
- Implements MKS-compatible motion, limits, heartbeat, and status handling.
- Adds frame interoperability tests and firmware documentation.
- Adds UNO R4 WiFi compilation CI.
| File | Description |
|---|---|
.github/workflows/firmware.yml |
Compiles the firmware in CI. |
firmware/tool_board/README.md |
Documents hardware, commands, and flashing. |
firmware/tool_board/mks_frames.h |
Implements MKS frame encoding and decoding. |
firmware/tool_board/tool_board.ino |
Controls four steppers over CAN. |
waybionic_teleop/test/test_tool_board_frames.py |
Verifies frame compatibility with the host. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Each drive must now confirm its mode, reply, enable and heartbeat settings before the arm moves. Unconfirmed settings are sent again every 0.5 s, so a lost frame can't leave a drive running without its heartbeat stop. Both buses report whether a frame went out, and a target the interface refused is not recorded as sent, so the next tick sends it again.
The per-motor command handling now lives in tool_motor.h, templated on the stepper driver. The sketch uses it with AccelStepper, and a teleop test compiles it with a fake stepper to check moves, completion and limit reports, the heartbeat stop, stops, enable and zero. A stop frame now uses its own acceleration, and acceleration 0 stops at once, as the manual says. The heartbeat also stops at once.
actionlint (shellcheck SC2046) flagged the unquoted uname expansion.
On a busy CI runner the teleop node could still be loading the robot description when the test pressed Start, so the press was lost and the arm never moved. The test now sends idle packets until teleop reports its group and fresh controller input.
The tests could press Start before the teleop node had loaded the robot description or received controller input, so the press was lost on a busy machine. They now send idle packets until teleop reports its group and fresh input. Same fix as on #28.
- Joint commands that stop mid-move, say because teleop exited, now stop every drive where it is instead of letting it run on to the last target. - The simulated drives integrate the full time since the last tick in 10 ms steps, so a pause longer than the heartbeat stops a moving drive, as on hardware. - Axis coordinates take the whole int24 range, down to -0x800000.
A second robot_description replaced an enabled teleop with a disabled one without sending a hold, so the drives kept their last target.
A position-only JointState leaves every velocity at zero, so the stale-command check never fired for an external publisher: the drives ran on to the last target after the publisher died. The check now looks only at how long it has been since the last command, latched to one stop and one warning so start-up and idle holds stay quiet.
absolute_axis rejected -0x800000, which the manual's 3-byte signed coordinate encodes. The node also capped the elapsed time it handed the drives at 100 ms, so a scheduler stall longer than the heartbeat never stopped a moving drive the way hardware would. The drive splits a long step into 10 ms motion steps and counts all of it toward the heartbeat.

Covers item 6 from the Oct 3 list: firmware for the 7th board, the biomedical tool board. Stacked on #31, which adds the set-zero frame.
What changes
firmware/tool_board/tool_board.ino: the four NEMA 8 steppers (8HS11-0204S on A4988 drivers) answer on CAN IDs 7 to 10 like MKS SERVO drives. The board handles:The host can therefore drive the tool with the code it already uses for the arm.
Positions: reported in MKS counts (16384 per turn), converted from the step count, since the motors have no encoders.
Speed limits: requests are capped at what a NEMA 8 can follow (60 rpm, 20000 steps/s²).
Travel limits: targets past a limit stop at the limit and report status 3 (stopped at end limit).
mks_frames.h: the frame code as plain C++.test_tool_board_frames.pycompiles it with g++ and checks it againstmks_can.pyboth ways: commands built bymks_candecode correctly, and the firmware's replies parse withmks_can.CI: the new
Tool Board Firmwareworkflow compiles the sketch witharduino/compile-sketchesfor the UNO R4 WiFi whenfirmware/changes.firmware/tool_board/README.md: pin table, command table, build and flash steps, and acansendbench check with the expected replies (generated withmks_can).Needs checking before use
nema8_2pair_LimitedRotationV3.inobefore driving the tool, because the cables limit how far each motor may turn.arm_drives.yamlas drives 7 to 10. Ten drives at 120 Hz use about half the 1 Mbit/s bus.How I tested it
arduino:renesas_uno1.6.0 and AccelStepper 1.64:docker compose --progress plain build test: all 781 tests pass, 14 of them new, none skipped.