# CAN Specification

**IGVC 2026**  
**Sooner Competitive Robotics**  
**Version 2026.01.14**

---

## 1. Overview

### 1.1 Endianness  

All multi‑byte values in this document use **little endian encoding**. This means the **least significant byte (LSB)** appears first on the bus. Most hardware devices will use little endian, and the software side is hard coded to use little endian. If this is not the case, Section 1.5 (Endian Conversions) lists some macros for doing those conversions.

### 1.2 CAN Bitrate

The standard CAN bitrate for SCR will be 125,000 (or 125kbps).

### 1.3 Scale Factors  

A **scale factor** converts raw integer data into a floating point and vice versa.  
- Example: If a temperature is sent as an `int16` with scale factor `0.1`, then  
$
temperature = raw \times 0.1
$  
To convert from a temperature (float or double) to that `int16`, you would do  
$
temperatureScaled = \(int16_t\)\(temperature \/ 0.1\)
$  
**NOTE:** The casting to an `int16_t` is important here. `c` will likely automatically cast if your variable type is an `int16_t` (or whatever the size of your data needs to be), but better to be safe than sorry.
<br/>  
Because a CAN Frame can only hold 8 bytes of data (as discussed in Section 1.6), it is preferred to use these "scale factors" whenever possible. While you do lose out on some precision, you almost always save on the size of your data.

### 1.4 Packet Examples

Examples on how to construct a packet from bytes and also convert a packet to bytes are shown below in `c`. This assumes that you have a variable named `can_frame` which contains the can frame data.
```c
// Sending a frame
SafetyLightsCommandMsg msg;
msg.mode = 2;
msg.color.r = 25; 
msg.color.g = 165; 
msg.color.b = 190; 
msg.speed = 1000;
uint8_t can_data[8] = {0}; // creates a buffer for us to copy our data into,
memcpy(can_data, &msg, sizeof(msg)); // takes the SafetyLightsCommandMsg struct and packs it into our buffer

// Receiving a frame
SafetyLightsCommandMsg msg;
memcpy(&msg, can_frame.data, sizeof(msg));
```

### 1.5 Endian Conversion

In the event that your device is not little endian and you must convert before sending, some helper functions written in `c` are provided below.
```c
/* 8-bit: no-op */
#define INT8_TO_LE(x) (x)

/* 16-bit swap */
#define INT16_TO_LE(x) \
    (uint16_t)((((uint16_t)(x) & 0x00FFu) << 8) | \
               (((uint16_t)(x) & 0xFF00u) >> 8))

/* 32-bit swap */
#define INT32_TO_LE(x) \
    (uint32_t)((((uint32_t)(x) & 0x000000FFu) << 24) | \
               (((uint32_t)(x) & 0x0000FF00u) << 8)  | \
               (((uint32_t)(x) & 0x00FF0000u) >> 8)  | \
               (((uint32_t)(x) & 0xFF000000u) >> 24))

/* 64-bit swap */
#define INT64_TO_LE(x) \
    (uint64_t)((((uint64_t)(x) & 0x00000000000000FFull) << 56) | \
               (((uint64_t)(x) & 0x000000000000FF00ull) << 40) | \
               (((uint64_t)(x) & 0x0000000000FF0000ull) << 24) | \
               (((uint64_t)(x) & 0x00000000FF000000ull) << 8)  | \
               (((uint64_t)(x) & 0x000000FF00000000ull) >> 8)  | \
               (((uint64_t)(x) & 0x0000FF0000000000ull) >> 24) | \
               (((uint64_t)(x) & 0x00FF000000000000ull) >> 40) | \
               (((uint64_t)(x) & 0xFF00000000000000ull) >> 56))

// Examples
uint16_t speed = INT16_TO_LE(1020); // Converts 1020 from big endian (assuming our system is big endian) to little endian.
```

### 1.6 CAN Frame Size Notes

As a note, we do not use CAN-FD we just use CAN. Because of this, the max frame size for your data must be between 1 and 8 bytes (or between 1 and 64 bits).

## 2. High‑Priority Messages  

CAN IDs **0–9**  
These messages contain critical information, generally only used for emergency stop and mobility messages.

### 2.1 Message: EStop

**CAN ID:** `0x0`  
**Purpose:** Sent whenever the EStop signal is driven high on the robot.

### 2.1 Message: Mobility Stop

**CAN ID:** `0x1`  
**Purpose:** Devices should pause motor outputs until the *Mobility Start* message is received.

### 2.1 Message: Mobility Start

**CAN ID:** `0x9`  
**Purpose:** Devices should resume motor outputs.

## 3. Medium‑Priority Messages  

CAN IDs **10–99**  
Used for general robot state, telemetry, and sensor data.

### 3.1 Message: Motor Command  

**CAN ID:** `0xA (10)`  
**Purpose:** Commands the robot to move. For the 2026 competition year, this is converted to swerve drive commands by the CAN Converter.

#### Data Layout

| Field | Type | Scale | Unit | Bytes | Notes |
|------|------|-------|------|-------|-------|
| **Forward Velocity** | int16 | 0.0001 | m/s | 0–1 | Positive is forward |
| **Sideways Velocity** | int16 | 0.0001 | m/s | 2–3 | Positive is left |
| **Angular Velocity** | int16 | 0.0001 | rad/s | 4–5 | Positive is CCW |

A scale factor of 0.0001 was chosen for Forward Velocity and Sideways Velocity because at a speed of 3.2m/s (or 7.15 mph), that would be 32000 represented as a signed short (or int16). This assumes that we never go faster than 3.2m/s, which should always be true.

A scale factor of 0.001 was chosen for Angular Velocity because at a rotational speed of 6.2 rad/s (or 360 deg/s), that would be 6200 represented as a signed short (or int16). This assumes that we never rotate faster than 6.2/s (or one full turn per second), which for safety should remain true.

#### C Representation

```c
typedef struct {
    int16_t forward_velocity;
    int16_t sideways_velocity;
    int16_t angular_velocity;
} MotorCommandMsg;

static inline float to_forward_velocity(uint16_t raw) { return raw * 0.0001f; }
static inline int16_t from_forward_velocity(float v) { return (int16_t)(v / 0.0001f); }

static inline float to_sideways_velocity(int16_t raw) { return raw * 0.0001f; }
static inline int16_t from_sideways_velocity(float a) { return (int16_t)(a / 0.0001f); }

static inline float to_angular_velocity(int16_t raw) { return raw * 0.001f; }
static inline int16_t from_angular_velocity(float t) { return (int16_t)(t / 0.001f); }
```

### 3.2 Message: Motor Odometry  

**CAN ID:** `0xB (11)`  
**Purpose:** Describes the delta of the robots last movement. That is, the measurement of the movement of the robot as provided by the encoders.  
**Frequency:** 20/s (50ms)

#### Data Layout

| Field | Type | Scale | Unit | Bytes | Notes |
|------|------|-------|------|-------|-------|
| **Delta X** | int16 | 0.0001 | m/s | 0–1 | Positive is forward |
| **Delta Y** | int16 | 0.0001 | m/s | 2–3 | Positive is left |
| **Delta Theta** | int16 | 0.001 | rad/s | 4–5 | Positive is CCW |

A scale factor of 0.0001 was chosen for all three variables because at the rate feedbacks are sent (typically 20hz), values will be extremely small and will almost always be less than 1. This allows them to comfortably fit within the range of a short **and** still maintain good accuracy. In practice this will let us move up to 65m/s and similarly spin up to 65 rad/s. If our robot is going that fast, we either are going to win a lot of defense money or need insurance.

#### C Representation

```c
typedef struct {
    int16_t delta_x;
    int16_t delta_y;
    int16_t delta_theta;
} MotorOdometryMsg;

static inline float to_delta(uint16_t raw) { return raw * 0.0001f; }
static inline int16_t from_delta(float v) { return (int16_t)(v / 0.0001f); }
```

### 3.3 Message: Safety Lights  

**CAN ID:** `0x14 (20)`  
**Purpose:** Commands the Safety Lights controller to set the safety lights to some state as described by the data below.  

#### Data Layout

| Field | Type | Scale | Unit | Bytes | Notes |
|------|------|-------|------|-------|-------|
| **Mode** | uint8 | 1 | N/A | 0 | |
| **Color** | uint8 | 1 | RGB | 1-3 | RGB (each byte is R, G, and B respectively. |
| **Speed** | uint16 | 1 | ms | 4-5 | The speed of the animation/mode. |

The **Mode** byte describes what the safety lights should be doing and valid options are listed as follows
- [Loading] = 0 | The safety lights are doing a "loading" animation (e.g. there only a few rgbs are on, cycling the strip). This is the default behavior and indicates the system is still booting. The color is indicated by the `color` parameter, a full "cycle" happens in a time determined by `speed`.
- [Solid] = 1 | The safety lights are a solid color indicated by the `color` parameter.
- [Blinking] = 2 | The safety lights are blinking (turning on and off) at a rate specified by the `speed` parameter and are colored as specified by the `color` parameter.

#### C Representation

```c
// Force the compiler to not add any padding after `color`. Probably not strictly needed, but makes everyone play nice even on different compilers/architectures.
#pragma pack(push, 1)
typedef struct Color {
  uint8_t r;
  uint8_t g;
  uint8_t b;
}

typedef struct {
    uint8_t mode;
    Color color;
    uint16_t speed;
} SafetyLightsCommandMsg;
#pragma pack(pop)
```

### 3.4 Message: HUB Telemetry  

**CAN ID:** `0x15 (21)`  
**Purpose:** Describes information that the HUB is collecting.
**Frequency:** 5/s (200ms)

#### Data Layout

| Field | Type | Scale | Unit | Bytes | Notes |
|------|------|-------|------|-------|-------|
| **Status** | bitmap | 1 | Connected | 0 | Each bit represents the connected/disconnected status of a device on the hub. |
| **Amperage** | int16map | N/A | mA | 1-7 | Each byte describes the measured current of a specific device in milliamps. |

In order to extract and convert a current measurement to 1 byte, the following c code can be used. Similar code will be provided for extracting information from the bitmap. By not splitting this into two messages we do lose some accuracy, although it isn't too bad and works out to around +/- 10 mA.

#### C Representation

```c
// Converts from mA (0 to 5000) to a unsigned byte (0 to 255)
#define SCALE_MA_TO_BYTE(x) ( (uint8_t)( ((x) <= 0) ? 0 : (((x) >= 5000) ? 255 : (uint8_t)((((x) * 255u) + 2500u) / 5000u))) ) 

// Converts from bytes to a float or integer
#define SCALE_BYTE_TO_MA_FLOAT(b) ( ((float)(b)) * (5000.0f / 255.0f) ) 
#define SCALE_BYTE_TO_MA_INT(b) ( ((uint32_t)(b) * 5000u + 127u) / 255u ) /* rounded integer mA */

// Bitmap helpers
#include <stdint.h>

/* Create a mask for device index d (0..7) */
#define STATUS_MASK(d)        ((uint8_t)(1u << (uint8_t)(d)))
#define STATUS_GET(status, d) ( ((uint8_t)(status) & STATUS_MASK(d)) ? 1u : 0u )
#define STATUS_SET(status, d) \
    do { (status) = (uint8_t)((status) | STATUS_MASK(d)); } while (0)
#define STATUS_CLEAR(status, d) \
    do { (status) = (uint8_t)((status) & (uint8_t)~STATUS_MASK(d)); } while (0)

typedef struct {
    uint8_t status;
    uint8_t current[7];
} HubTelemetryMsg;

// Example of reading and writing device 3's status
HubTelemetryMsg msg;
STATUS_SET(msg.status, 3);
if (STATUS_GET(msg.status, 3))
{
  // This will only be reached if Device 3 is connected, which it would
  // be as we declared that above
}

// Example of setting and getting the current of device 5 (or index 4)
msg.current[4] = SCALE_MA_TO_BYTE(4095); // This states that device 5 is drawing 4095 mA of current
int current = SCALE_BYTE_TO_MA_INT(msg.current[4])
// The `current` variable here would read 4095
```

## 4. Configuration Messages  

CAN IDs **1000–1399**  
These messages are for conbus, our configuration system for hardware devices on the robot.

TODO: Fill in