Skip to content

2. FRC CAN Device Specifications

Nathan zhou edited this page Feb 3, 2026 · 11 revisions

Before reading this article, please read the official FRC CAN Device Specifications on WPILib Docs.

https://docs.wpilib.org/en/stable/docs/software/can-devices/can-addressing.html

How FRC CAN bus works

The 'CAN ID' we talked about when setting up motor controllers (Spark, Kraken, PDP, etc) is a number from 0-63. This is not the CAN identifier (message ID) used in CAN data layer. FRC Use a very specific scheme outlined in FRC CAN specification to convert certain information to CAN identifier. All device connected to roboRIO must follow this scheme when determine what ID to send data to and read form.

CAN identifier is calculated from device Type ID, manufacturer ID, API ID (in the documentation it talked about API Class and Index but doesn't matter and it is just a suggestion. Each device will have its unique API ID designed for its use) and device number (0-63 as the traditional 'CAN ID' we talked about in FRC).

You must generate your CAN identifier (message ID) using this very specific way. A device will have multiple CAN message sent to and from roboRIO (and possibly to other non-roboRIO device). These different messages will share the same device type ID, manufacturer ID and device number. API ID will change based on use.

Sample encoding code

// === CAN Constants ===
#define DEVICE_TYPE_ID        0x0A  //DONOT CHANGE
#define MANUFACTURER_ID  0x08  //DONOT CHANGE
#define DEVICE_NUMBER    33  // Device Number 0-63
#define STATUS_API_ID        0x180
#define COLOR_SENSOR_API_ID  0x184
#define CONTROL_API_ID       0x190
#define HEARTBEAT_ID         0x01011840

uint32_t makeCANMsgID(uint8_t deviceTypeID, uint8_t manufacturerID, uint16_t apiID, uint8_t deviceNumber) {
  return ((uint32_t)(deviceTypeID & 0xFF) << 24) |
         ((uint32_t)(manufacturerID & 0xFF) << 16) |
         ((uint32_t)(apiID & 0x3FF) << 6) |
         (deviceNumber & 0x3F);
}

Sample decoding code

struct DecodedCANID {
  uint8_t deviceTypeID;
  uint8_t manufacturerID;
  uint16_t apiID;
  uint8_t deviceNumber;
};

DecodedCANID decodeCANMsgID(uint32_t can_id) {
  DecodedCANID decoded;

  decoded.deviceTypeID = (can_id >> 24) & 0xFF;
  decoded.manufacturerID = (can_id >> 16) & 0xFF;
  decoded.apiID = (can_id >> 6) & 0x3FF;
  decoded.deviceNumber = can_id & 0x3F;

  return decoded;
}

uint32_t msgID = makeCANMsgID(0x0A, 0x08, 0x184, 33);
DecodedCANID result = decodeCANMsgID(msgID);

Serial.printf("Device Type ID: 0x%02X\n", result.deviceTypeID);
Serial.printf("Manufacturer ID: 0x%02X\n", result.manufacturerID);
Serial.printf("API ID: 0x%03X\n", result.apiID);
Serial.printf("Device Number: %d\n", result.deviceNumber);

CAN Heartbeat

Upon robot code startup (code go green on Driver Station), roboRIO will send a universal heartbeat on CAN message ID 0x01011840.

Per FRC Game manual R714, you should not send any CAN data packet on the above message ID.

CAN Device Number vs CAN Message ID (Why this is confusing)

In FRC documentation and tooling, the phrase “CAN ID” is overloaded and often used incorrectly. This is the root of most confusion for newcomers.

There are three different concepts that people casually call “CAN ID”:

  1. CAN Device Number (what REV / CTRE tools configure)
  2. API ID (what kind of message this is)
  3. CAN Message ID (the actual 29-bit identifier on the wire)

Only #1 is user-configurable in vendor tools.


1. CAN Device Number (what users actually set)

When you open REV Hardware Client or CTRE Phoenix Tuner and set a device to “CAN ID = 33”, you are not setting a CAN message ID.

You are setting the CAN Device Number:

#define DEVICE_NUMBER 33  // valid range: 0–63

This value:

  • Identifies which physical device you are talking to
  • Is appended into every CAN message sent to or from that device
  • Is not unique by itself on the bus (manufacturer + device type matter)

Think of this as a node address, not a message identifier.


2. API ID (what the message means)

The API ID describes what kind of message is being sent:

#define STATUS_API_ID        0x180
#define COLOR_SENSOR_API_ID  0x184
#define CONTROL_API_ID       0x190

Examples:

  • 0x180 → status / telemetry
  • 0x190 → control command
  • 0x184 → sensor data

API IDs are defined by:

  • WPILib HAL
  • Vendor firmware (REV / CTRE)
  • Your own custom device firmware

Users do not set API IDs in vendor software. API IDs are fixed in firmware and are only created by the vendor or by developers building custom CAN devices who design their own APIs based on their device’s functionality.


3. CAN Message ID (what is actually on the wire)

This is the 29-bit extended CAN identifier that the controller transmits.

In FRC, the message ID is a packed structure:

[ device type ][ manufacturer ][ api id ][ device number ]

Your helper function shows this clearly:

uint32_t makeCANMsgID(uint8_t deviceTypeID,
                      uint8_t manufacturerID,
                      uint16_t apiID,
                      uint8_t deviceNumber) {
  return ((uint32_t)(deviceTypeID & 0xFF) << 24) |
         ((uint32_t)(manufacturerID & 0xFF) << 16) |
         ((uint32_t)(apiID & 0x3FF) << 6) |
         (deviceNumber & 0x3F);
}

This means:

Field Bits Purpose
deviceTypeID 5 Device type (motor, sensor, etc.)
manufacturerID 8 Vendor or custom device
apiID 10 Message meaning
deviceNumber 6 Physical device number

The CAN Message ID is computed, not assigned.


Why vendor tools feel misleading

Vendor software labels this field as “CAN ID”, but what they really mean is:

“Set the device number portion of the CAN message ID”

They hide:

  • Device type
  • Manufacturer ID
  • API ID

Because those are fixed by firmware.

So when someone says:

“Set the CAN ID to 33”

What they really mean is:

“Set the CAN Device Number to 33”


Why this distinction matters for custom CAN devices

When you build your own ESP32 / Arduino CAN device:

  • You must generate the full CAN Message ID yourself

  • The roboRIO will only listen if:

    • Manufacturer ID matches
    • API ID matches
    • Device number matches

If any part is wrong:

  • Messages will silently disappear
  • No error will be reported
  • The bus might still look “healthy”

This is why understanding the difference is critical for debugging.


Key takeaway

CAN Device Number ≠ CAN Message ID

  • Device Number → user-assigned node address (0–63)
  • API ID → message type
  • CAN Message ID → packed identifier used on the wire

Once you separate these concepts, FRC CAN suddenly makes a lot more sense.

Clone this wiki locally