The host sends motor commands over CAN, and an ESP32 drives a one-degree-of-freedom gripper using SimpleFOC. This extends the ESP32 communication setup in CAN 03; the Arduino sketch is a historical motor-control prototype. Apply the source corrections below before using it as a build example.

This work was completed with Roboligent using its Optimo robot manipulator.

Video

Optimo manipulator pushing and grasping a Jenga block.

Hardware

optimo
gripper
Left: Optimo arm. Right: one-DOF robotic gripper.

Wiring

motor-wiring
Wiring diagram for motor control.

The transceiver connection follows CAN 03, but the motor sketch uses TX GPIO 26 and RX GPIO 27, rather than TX 17/RX 16. Wire those pins or change both definitions to match the board. Some TJA1050 modules and USB-to-CAN adapters include a switchable 120 Ω termination resistor. With one 120 Ω terminator at each physical end, the powered-off bus should measure approximately 60 Ω between CAN-H and CAN-L.

Connect the motor phases to the SimpleFOC Mini's M1, M2, and M3 outputs. Phase order and sensor direction must be consistent with the FOC configuration; an incorrect combination can reverse the direction or prevent reliable commutation. Rerun the library's motor/sensor alignment procedure after changing the phase wiring. The driver also requires a separate motor supply; this test used 12 V for the HT2205.

Connect IN1, IN2, IN3, and EN to the configured ESP32 GPIO pins, and connect the logic grounds.

Power the SPI encoder at a voltage supported by both the sensor and the ESP32 logic interface. Connect MOSI, MISO, SCLK, and chip select to the configured SPI pins. Default pin assignments vary by ESP32 board. The following sketch prints the Arduino core's defaults:

void setup() {

  Serial.begin(115200);
  Serial.print("MOSI: ");
  Serial.println(MOSI);
  Serial.print("MISO: ");
  Serial.println(MISO);
  Serial.print("SCK: ");
  Serial.println(SCK);
  Serial.print("SS: ");
  Serial.println(SS);
}

void loop() {
}

Follow the magnetic encoder datasheet's requirements for magnet geometry, alignment, field strength, and air gap. Excessive axial or radial misalignment can reduce accuracy or produce invalid readings. Choose the gap together with the specified magnet geometry.

Multiple SPI encoders may share MOSI, MISO, and SCLK while using separate chip-select lines. Keep the wiring short, provide a solid return path, and reduce the clock if ringing or read errors appear. The AS5048A specifies a minimum serial-clock period of 100 ns, corresponding to a 10 MHz maximum clock. An 80 MHz clock exceeds that limit. The SimpleFOC v2.3.3 AS5048 SPI configuration uses a 1 MHz default. Versioned sensor configuration Splitting devices across the ESP32's SPI peripherals can also simplify timing and chip-select management.

Prepare the Arduino Sketch

Install the SimpleFOC and Arduino-CAN libraries in the Arduino IDE. The historical sketch does not pin their versions, so check API compatibility for the versions you install.

Correct the CAN ID macros before compiling. Their historical definitions include = and a semicolon, which produce invalid C++ at the call sites. Use:

#define CAN_RX_ID 0x11
#define CAN_TX_ID 0x21

The motor sketch already calls CAN.begin(500E3); its comment saying 250 kbit/s is stale. Use 500 kbit/s on the host for this example, with the same verification procedure as CAN 03.

comm-diagram
Communication diagram for motor control.

The linked prototype receives a scalar motor target and returns the encoder angle in response to a matching command. Its variable is named target_torque, but that name does not establish the target's physical unit. Classic CAN provides at most 8 payload bytes per frame, so one 64-bit value fills the entire payload.

This prototype assumes both endpoints use the same 64-bit IEEE-754 representation and byte order. Under those conditions, memcpy copies a double into an eight-byte buffer without violating C++ aliasing rules:

void packDouble(double value, uint8_t dataBuffer[8]) {
    static_assert(sizeof(double) == 8, "Protocol requires a 64-bit double");
    memcpy(dataBuffer, &value, 8);
}

This copy leaves byte order and floating-point representation implicit. Define them explicitly when the endpoints may differ, and document the scaling used by the protocol. For example, torque and position can be converted to fixed-width integers with documented units before serialization. This also leaves room for status flags, sequence numbers, or multiple signals in one frame.

The sketch supplies phase resistance to BLDCMotor, uses torque motion control, and passes the received scalar directly to motor.move(). In SimpleFOC v2.3.3's voltage torque mode with known resistance, this target is an estimated current in amperes. The library estimates the required voltage; the example does not close a measured-current loop. Versioned motor implementation · SimpleFOC estimated-current mode

To give the host command units of N·m, define and implement iqdes=τdes/Kti_q^{\mathrm{des}}=\tau^{\mathrm{des}}/K_t using a torque constant and coordinate convention valid for that motor. The historical sketch leaves this conversion commented out. Its numeric ±0.5 clamp must therefore not be described as a verified ±0.5 N·m torque limit.

The sketch also returns zero when no matching command is read. It does not implement a persistent setpoint with an explicit command watchdog. Define the command lifetime, limits, and communication-loss behavior before adapting this prototype for a robot. The I/O scheduling discussion in CAN 06 separates those responsibilities.

The host can use a small C++ SocketCAN interface to encode commands and decode feedback. The Linux SocketCAN documentation is the authoritative reference for the socket API.

Video

Motor-control bench demonstration with SocketCAN. The eight-byte format above describes the linked sketch; this historical video does not establish its payload format.

The belt transmission converts motor rotation into symmetric linear motion of the two gripper jaws.

gripper_mech
Single-motor parallel-gripper mechanism.

The arm and gripper trajectories were recorded through kinesthetic teaching by manually guiding the robot. Replaying those trajectories produced the Jenga pushing and grasping sequence shown below.

Video

Optimo manipulator pushing and grasping a Jenga block.

References