Control CAN-based motor controllers from Arduino boards using a cheap MCP2515 CAN shield. CanControl currently supports:
- REV Robotics Spark MAX (FRC brushless/brushed motor controller)
- CTRE Talon SRX (PercentOutput)
- CTRE Victor SPX (PercentOutput)
All communication goes through an MCP2515 controller on the Arduino SPI bus, so you do not need a native CAN-capable MCU.
- High-level C++ wrappers:
CanControl::SparkMaxCanControl::TalonSrxMotorCanControl::VictorSpxMotor
- Auto-generated low-level Spark MAX CAN protocol based on REV's published - JSON spec (see
gen.pyandthird_party/REV-Specs/). - Simple FRC-style heartbeat frame generator.
- CTRE interfacing based on the only available public documentation of their CAN protocol
- Works on Arduino Mega 2560 and Uno (via MCP2515 breakout).
- Arduino compatible board
- Tested with: Arduino Mega 2560 (
env:mega) - Also configured for: Arduino Uno (
env:uno)
- Tested with: Arduino Mega 2560 (
- MCP2515 CAN controller breakout (e.g. "TJA1050 + MCP2515" boards)
- SPI connected to the Arduino (SCK/MOSI/MISO)
CSpin configurable viaMCP2515_CS_PIN(defaults to 53 on Mega, 10 on Uno)
- CAN motor controllers on the same CAN bus:
- Spark MAX (REV Robotics)
- Talon SRX / Victor SPX (Cross The Road Electronics) (Needs to be FRC unlocked, see instructions below)
Wiring is the usual MCP2515+CAN transceiver wiring (CANH/CANL twisted pair, proper termination at the ends of the bus).
This repo is a PlatformIO project. You can use it directly, or add it as a dependency to another PlatformIO project.
git clone https://github.com/willGuimont/CanControl.git
cd CanControl
git submodule update --init --recursive
# Build for Arduino Mega 2560
pio run -e mega
# (Optional) Upload depending on your upload_port
pio run -e mega -t uploadThe main example sketch is in src/main.cpp.
Quick steps to run the example sketch:
- Build & upload (PlatformIO):
pio run -e megapio run -e mega -t upload
- Open the serial monitor at 115200:
pio device monitor -e mega -b 115200
What the example does:
- Configures the
MCP2515CAN controller and sets the bitrate. - Creates a
SparkMaxandTalonSrxMotorinstance bound to theMCP2515. - Sends periodic FRC-style heartbeat frames and the CTRE global-enable frame so controllers remain enabled.
- Resets Spark MAX user parameters once on startup.
- Accepts simple serial commands to control the motor:
s<float>— set speed percent (e.g.s0.5= 50%)p<float>— set position (Spark MAX)h— print help
Heartbeat and CTRE enable API examples:
// send a default zero heartbeat (FRC style)
CanControl::send_heartbeat(mcp2515, CanControl::heartbeat::RobotState{});
// send CTRE global enable (Talon/Victor)
CanControl::TalonSrxMotor::send_global_enable(mcp2515, true);Use the serial commands to drive the motor at low speed first and verify wiring.
- No motor response: verify
MCP2515_CS_PINmatches wiring and the MCP2515 is in normal mode. - Bitrate mismatch: ensure
setBitrate(...)is set toCAN_1000KBPSfor FRC controllers. - CAN termination: ensure 120Ω termination at both ends of the bus.
- CTRE devices not responding: verify they are FRC-unlocked (see CTRE section below).
- Use the serial monitor to inspect startup messages printed by the example (MCP2515 reset/status, PID set results).
Safety: test at low duty (e.g. s0.1) with motors disconnected from mechanisms or props until behavior is verified.
In your own platformio.ini:
[env:mega]
platform = atmelavr
framework = arduino
board = megaatmega2560
lib_deps =
https://github.com/willGuimont/CanControl.gitThen in your code:
#include "sparkmax.h"
static MCP2515 mcp2515(53); // CS pin
static constexpr uint8_t kMotorId = 11;
static CanControl::SparkMax motor(mcp2515, kMotorId);
mcp2515.reset();
mcp2515.setBitrate(CAN_1000KBPS, MCP_8MHZ);
mcp2515.setNormalMode();
// Optional: reset user-configurable parameters on the Spark MAX
motor.reset_safe_parameters();
// Run the motor at 25% duty cycle
motor.set_duty_cycle(0.25f);Using CTRE devices requires that they be "FRC unlocked" to accept non-FRC frames. The FRC lock will be enabled by default and be persistent across power cycles if the device was ever connected to an FRC roboRIO. To unlock the device, you need to disconnect it from the roboRIO and power it while holding the reset button for about 5 seconds until the LED blinks green.
For CTRE devices there are two layers:
- Low-level helpers in
include/low_level/low_ctrelectronics.h - High-level wrappers:
CanControl::TalonSrxMotorininclude/talonsrx.hCanControl::VictorSpxMotorininclude/victorspx.h
Example:
#include "talonsrx.h"
MCP2515 mcp2515(53);
CanControl::TalonSrxMotor talon(mcp2515, 3); // CAN ID 3
// MCP2515 init...
// Send CTRE "global enable" (non-FRC frame)
CanControl::TalonSrxMotor::send_global_enable(mcp2515, true);
// 50% PercentOutput
talon.set_percent_output(0.5f);VictorSpxMotor works the same way but internally calls a different builder
for the Victor SPX arbitration ID.
The low-level Spark MAX protocol bindings are generated from
doc/spark-frames-2.0.0-dev.11.json (available from REVrobotics/REV-Specs) by gen.py and live in:
include/low_level/low_sparkmax.hsrc/low_level/low_sparkmax.cpp
These files are auto-generated (see the header comment) and should not be
edited by hand. Instead, update the JSON spec or generator and re-run gen.py.
The high-level SparkMax class in include/sparkmax.h wraps the generated
SparkCanDevice and provides friendly methods:
set_duty_cycle(float duty)set_position(float position)set_velocity(float velocity)reset_safe_parameters()stop()
This project uses PlatformIO's Unity-based test framework.
Run tests on your host (native):
pio test -e native -vRun embedded tests on the board (e.g. Mega 2560):
pio test -e mega -vThe native tests live under test/test_native, and embedded tests under
test/test_embedded.
MIT. See the LICENSE file for details.