Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/vedderb/bldc/llms.txt

Use this file to discover all available pages before exploring further.

The VESC firmware supports CAN bus communication for controlling multiple VESC-based devices on a shared bus. The CAN mode can be selected in VESC Tool under App Settings -> General -> CAN Mode.

CAN modes

VESC

Default VESC CAN bus. Required for CAN forwarding and configuring multiple VESC-based devices using VESC Tool.

UAVCAN

Basic implementation of uavcan.equipment.esc. See the DroneCAN documentation for details.

Comm Bridge

Bridges the CAN bus to commands. Useful for using VESC Tool as a generic CAN interface and debugger.

Unused

CAN frames are not processed and are ignored. Custom applications and scripts can still process CAN frames. Similar to Comm Bridge, but received frames are not forwarded using commands.
The default and recommended CAN mode is VESC.

VESC ID configuration

Each VESC-based device on the bus must have a unique ID. You can set the VESC ID in VESC Tool under App Settings -> General -> VESC ID. A device only accepts incoming CAN frames that match its configured ID.

Timeout

By default, the VESC stops the motor when nothing has been received on the CAN bus for more than 0.5 seconds.
Do not disable the timeout in production systems. If communication is interrupted unexpectedly, the motor will continue running with no way to stop it via CAN.
You can change the timeout value in VESC Tool under App Settings -> General. Setting it to 0 disables the timeout entirely. A timeout brake current can also be configured. When timeout occurs, the VESC applies this current to brake the motor. The default is 0, which simply releases the motor with no braking force.
To prevent timeouts, send commands continuously at a fixed rate — 50 Hz is recommended.

Frame format

All VESC CAN frames use 29-bit extended IDs. The receiver ID and command ID are both embedded in the extended frame ID:
BitsFieldDescription
B28 – B16UnusedAlways zero
B15 – B8Command IDThe CAN_PACKET_ID value
B7 – B0VESC IDTarget device ID
The extended ID is constructed as:
uint32_t eid = controller_id | ((uint32_t)command_id << 8);

Single-frame commands

Simple CAN commands each fit in a single CAN frame. The data payload is always a 32-bit big-endian signed integer representing the command argument, multiplied by the scaling factor for that command. Example: Setting 51 A of current (CAN_PACKET_SET_CURRENT, command ID 1, scaling 1000) on VESC ID 23:
FieldValue
Extended ID0x0117
B00x00
B10x00
B20xC7
B30x38
51 * 1000 = 51000 = 0x0000C738

Available simple commands

CommandIDScalingUnitRangeDescription
CAN_PACKET_SET_DUTY0100000% / 100-1.0 to 1.0Duty cycle
CAN_PACKET_SET_CURRENT11000A-MOTOR_MAX to MOTOR_MAXMotor current
CAN_PACKET_SET_CURRENT_BRAKE21000A-MOTOR_MAX to MOTOR_MAXBraking current
CAN_PACKET_SET_RPM31RPM-MAX_RPM to MAX_RPMMotor RPM
CAN_PACKET_SET_POS41000000Degrees0 to 360Position
CAN_PACKET_SET_CURRENT_REL10100000% / 100-1.0 to 1.0Relative current
CAN_PACKET_SET_CURRENT_BRAKE_REL11100000% / 100-1.0 to 1.0Relative braking current
CAN_PACKET_SET_CURRENT_HANDBRAKE121000A-MOTOR_MAX to MOTOR_MAXHandbrake current
CAN_PACKET_SET_CURRENT_HANDBRAKE_REL13100000% / 100-1.0 to 1.0Relative handbrake current
Commands sent outside the valid range are clamped at the limit. For example, if the maximum braking current is configured to 50 A and you send 60 A with CAN_PACKET_SET_CURRENT_BRAKE, the firmware will use 50 A.

C code example

The following implementation shows how to construct and send all simple CAN commands. Provide your own can_transmit_eid function that writes an extended-ID frame to the bus.
#include <stdint.h>

// Provide this function for your hardware
void can_transmit_eid(uint32_t id, const uint8_t *data, uint8_t len) {
    // hardware-specific CAN transmit
}

typedef enum {
    CAN_PACKET_SET_DUTY                    = 0,
    CAN_PACKET_SET_CURRENT                 = 1,
    CAN_PACKET_SET_CURRENT_BRAKE           = 2,
    CAN_PACKET_SET_RPM                     = 3,
    CAN_PACKET_SET_POS                     = 4,
    CAN_PACKET_SET_CURRENT_REL             = 10,
    CAN_PACKET_SET_CURRENT_BRAKE_REL       = 11,
    CAN_PACKET_SET_CURRENT_HANDBRAKE       = 12,
    CAN_PACKET_SET_CURRENT_HANDBRAKE_REL   = 13,
    CAN_PACKET_MAKE_ENUM_32_BITS           = 0xFFFFFFFF,
} CAN_PACKET_ID;

void buffer_append_int16(uint8_t *buffer, int16_t number, int32_t *index) {
    buffer[(*index)++] = number >> 8;
    buffer[(*index)++] = number;
}

void buffer_append_int32(uint8_t *buffer, int32_t number, int32_t *index) {
    buffer[(*index)++] = number >> 24;
    buffer[(*index)++] = number >> 16;
    buffer[(*index)++] = number >> 8;
    buffer[(*index)++] = number;
}

void buffer_append_float16(uint8_t *buffer, float number, float scale, int32_t *index) {
    buffer_append_int16(buffer, (int16_t)(number * scale), index);
}

void buffer_append_float32(uint8_t *buffer, float number, float scale, int32_t *index) {
    buffer_append_int32(buffer, (int32_t)(number * scale), index);
}

void comm_can_set_duty(uint8_t controller_id, float duty) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_int32(buffer, (int32_t)(duty * 100000.0), &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_DUTY << 8), buffer, send_index);
}

void comm_can_set_current(uint8_t controller_id, float current) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_int32(buffer, (int32_t)(current * 1000.0), &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_CURRENT << 8), buffer, send_index);
}

// Same as comm_can_set_current, but also sets an off delay (seconds).
// The off delay keeps the current controller active briefly after the
// current drops below the minimum threshold.
void comm_can_set_current_off_delay(uint8_t controller_id, float current, float off_delay) {
    int32_t send_index = 0;
    uint8_t buffer[6];
    buffer_append_int32(buffer, (int32_t)(current * 1000.0), &send_index);
    buffer_append_float16(buffer, off_delay, 1e3, &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_CURRENT << 8), buffer, send_index);
}

void comm_can_set_current_brake(uint8_t controller_id, float current) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_int32(buffer, (int32_t)(current * 1000.0), &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_CURRENT_BRAKE << 8), buffer, send_index);
}

void comm_can_set_rpm(uint8_t controller_id, float rpm) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_int32(buffer, (int32_t)rpm, &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_RPM << 8), buffer, send_index);
}

void comm_can_set_pos(uint8_t controller_id, float pos) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_int32(buffer, (int32_t)(pos * 1000000.0), &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_POS << 8), buffer, send_index);
}

void comm_can_set_current_rel(uint8_t controller_id, float current_rel) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_float32(buffer, current_rel, 1e5, &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_CURRENT_REL << 8), buffer, send_index);
}

void comm_can_set_current_brake_rel(uint8_t controller_id, float current_rel) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_float32(buffer, current_rel, 1e5, &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_CURRENT_BRAKE_REL << 8), buffer, send_index);
}

void comm_can_set_handbrake(uint8_t controller_id, float current) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_float32(buffer, current, 1e3, &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_CURRENT_HANDBRAKE << 8), buffer, send_index);
}

void comm_can_set_handbrake_rel(uint8_t controller_id, float current_rel) {
    int32_t send_index = 0;
    uint8_t buffer[4];
    buffer_append_float32(buffer, current_rel, 1e5, &send_index);
    can_transmit_eid(controller_id |
            ((uint32_t)CAN_PACKET_SET_CURRENT_HANDBRAKE_REL << 8), buffer, send_index);
}

Status messages

The VESC can broadcast periodic status messages on the CAN bus. Enable them in VESC Tool under App Settings -> General -> CAN Status Messages Rate x. Two independent rate groups are available. Each group can carry any combination of the six status message types. Using two rates lets you transmit high-priority telemetry frequently and lower-priority data at a reduced rate, keeping bus utilization in check.

Available status messages

CommandIDData
CAN_PACKET_STATUS9ERPM, Current, Duty Cycle
CAN_PACKET_STATUS_214Ah Used, Ah Charged
CAN_PACKET_STATUS_315Wh Used, Wh Charged
CAN_PACKET_STATUS_416Temp FET, Temp Motor, Current In, PID position
CAN_PACKET_STATUS_527Tachometer, Voltage In
CAN_PACKET_STATUS_658ADC1, ADC2, ADC3, PPM

Status message encoding

BytesDataUnitScale
B0 – B3ERPMRPM1
B4 – B5CurrentA10
B6 – B7Duty Cycle% / 1001000
BytesDataUnitScale
B0 – B3Amp Hours UsedAh10000
B4 – B7Amp Hours ChargedAh10000
BytesDataUnitScale
B0 – B3Watt Hours UsedWh10000
B4 – B7Watt Hours ChargedWh10000
BytesDataUnitScale
B0 – B1FET Temperature°C10
B2 – B3Motor Temperature°C10
B4 – B5Input CurrentA10
B6 – B7PID PositionDegrees50
BytesDataUnitScale
B0 – B3TachometerEREV6
B4 – B5Input VoltageV10
BytesDataUnitScale
B0 – B1ADC1V1000
B2 – B3ADC2V1000
B4 – B5ADC3V1000
B6 – B7PPM% / 1001000

Full CAN packet ID reference

The complete CAN_PACKET_ID enum is defined in datatypes.h. Multi-frame commands (IDs 5–8) are used internally for tunneling full VESC serial protocol packets over CAN.
IDNameDescription
0CAN_PACKET_SET_DUTYSet duty cycle
1CAN_PACKET_SET_CURRENTSet motor current
2CAN_PACKET_SET_CURRENT_BRAKESet braking current
3CAN_PACKET_SET_RPMSet RPM
4CAN_PACKET_SET_POSSet position
5CAN_PACKET_FILL_RX_BUFFERMulti-frame: fill receive buffer
6CAN_PACKET_FILL_RX_BUFFER_LONGMulti-frame: fill receive buffer (long)
7CAN_PACKET_PROCESS_RX_BUFFERMulti-frame: process receive buffer
8CAN_PACKET_PROCESS_SHORT_BUFFERMulti-frame: process short buffer
9CAN_PACKET_STATUSStatus message 1
10CAN_PACKET_SET_CURRENT_RELSet relative current
11CAN_PACKET_SET_CURRENT_BRAKE_RELSet relative braking current
12CAN_PACKET_SET_CURRENT_HANDBRAKESet handbrake current
13CAN_PACKET_SET_CURRENT_HANDBRAKE_RELSet relative handbrake current
14CAN_PACKET_STATUS_2Status message 2
15CAN_PACKET_STATUS_3Status message 3
16CAN_PACKET_STATUS_4Status message 4
17CAN_PACKET_PINGPing device
18CAN_PACKET_PONGPing response
27CAN_PACKET_STATUS_5Status message 5
31CAN_PACKET_SHUTDOWNShutdown device
58CAN_PACKET_STATUS_6Status message 6
63CAN_PACKET_UPDATE_BAUDUpdate CAN baud rate
For the authoritative list see datatypes.h and the implementation in comm/comm_can.c.

Build docs developers (and LLMs) love