Skip to main content
CAN Bus Motor Control System

Productive Robotics

CAN Bus Motor Control System

Introduced CAN bus motor control to a company whose entire codebase was EtherCAT-only. Built a complete real-time CAN bus control stack from scratch for GIM6010 pancake motors on ODrive controllers — from raw Linux CAN sockets (PF_CAN/SOCK_RAW) running at 500Hz through ROS service integration. ~80 commits across ODrive, EtherCAT master, and gripper packages. Added an entirely new fieldbus protocol to the company's robot platform, integrating CAN motors alongside the existing EtherCAT infrastructure. This is the lowest-level interface between software and physical actuators — the same ground-up motor control work done at Boston Dynamics, Tesla Optimus, and Agility Robotics.

Productive Robotics' entire motor control stack was EtherCAT-only before I joined. The new FryBot platform used GIM6010 pancake motors on ODrive controllers that communicate over CAN bus — a protocol the company had never used. I built the complete CAN bus integration from scratch and wired it into the existing EtherCAT control loop, giving the company a hybrid EtherCAT + CAN motor control architecture. Designed ODriveManager as a thread-safe C++ singleton managing a raw Linux CAN socket (PF_CAN/SOCK_RAW/CAN_RAW). Opens a kernel-level socket, binds to the CAN network interface, and runs a dedicated rxThread() that performs blocking read() calls on the socket file descriptor. Incoming CAN frames are parsed and routed to the correct ODriveMotor instance by extracting the device address from the can_id field. Motor ownership managed via std::map<uint8_t, std::unique_ptr<ODriveMotor>> with non-owning raw pointer returns to callers. Thread safety enforced with std::mutex for singleton creation and std::lock_guard throughout all public methods — critical because the CAN bus is shared between the 1kHz EtherCAT control loop and asynchronous ROS service calls.

Each ODriveMotor controller is called at 1kHz by the EtherCAT master's real-time loop, with a frame_skip_counter_ % 2 mechanism limiting actual CAN bus traffic to ~500Hz to avoid bus saturation. Implements three control modes following the GIM6010 Protocol V3.07b0: position control (radians → encoder counts via 36:1 gear ratio, packed as int32 for 0xC2 absolute position commands), torque control (Nm → motor amps via torque constant Kt = 0.097 Nm/A, sent as 0xC0 commands), and velocity control (rad/s → 0.01 RPM units for 0xC1 commands). Also implements MIT Mini Cheetah-style control via sendMITMessage() using a separate CAN ID format (Bit[10]=1) for direct motor current control — the same protocol used in MIT's Mini Cheetah quadruped robot.

Built a complete fault tolerance and recovery system: heartbeat monitoring tracks last_heartbeat_ per node with 2-second timeout for motor presence detection. Power-cycle recovery detects when a motor loses power via response timestamp gaps (last_move_response_time_), then automatically restores cached PID parameters (Kp/Ki for both position and velocity loops) by re-sending them over CAN commands 0xB60xB9 through ResetPIDsIfNeeded(). Fault decoding parses 0xAE status responses into bit-field fault codes — Voltage, Current, Temperature, Encoder, Hardware, Software — each mapped to human-readable tooltip descriptions for the motor dashboard UI. Multi-data feedback via 0xA4 requests after every move command returns temperature, current, speed, and position in a single response frame.

Implemented async request/response patterns using PendingResponse structs containing std::condition_variable and std::mutex pairs, enabling blocking CAN commands that wait for hardware confirmation (e.g., gripper position commands that need acknowledgment before the executive proceeds). Built a custom CAN protocol for a 3-position industrial gripper: release (0x01 → CAN ID 0x121), spring-grab (0x020x122), hard-grab (0x030x123), with basket sensor detection parsed from response frames (DLC=2: [cmd_echo, basket_status]). Fixed a race condition where has_basket was accessed before super().__init__() completed, crashing the gripper during detect_grip calls.

C++CAN BusReal-TimeMotor ControlLinuxROS