Protocol Specification

CRUMBS Protocol v0.10
Variable-length IΒ²C messaging with CRC-8 validation


Wire Format

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  type_id β”‚  opcode  β”‚ data_len β”‚   data[0..N]    β”‚   crc8   β”‚
β”‚ (1 byte) β”‚ (1 byte) β”‚ (1 byte) β”‚ (0–27 bytes)    β”‚ (1 byte) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Message size: 4–31 bytes
Maximum payload: 27 bytes (constrained by Arduino Wire’s 32-byte buffer)


Field Specifications

Field Size Range Available Description
address 1 byte 0x08-0x77 112 IΒ²C address (not serialized)
type_id 1 byte 0x01-0xFF 255 Device type identifier
opcode 1 byte 0x01-0xFD 253 Command identifier (per type_id)
data_len 1 byte 0-27 28 Payload byte count
data[] 0–27 bytes N/A N/A Opaque payload
crc8 1 byte N/A N/A CRC-8 (polynomial 0x07)

Notes:

  • address field exists in struct but is not serialized (used for routing only)
  • Maximum 31 bytes ensures Arduino Wire compatibility
  • CRC covers type_id, opcode, data_len, and data[] (excludes address and CRC itself)
  • Payload is opaque bytes; applications encode floats, ints, structs, etc. as needed

Address Space

Range Decimal Status
0x00–0x07 0–7 Reserved (IΒ²C specification)
0x08–0x77 8–119 Available (112 addresses)
0x78–0x7F 120–127 Reserved (IΒ²C specification)

Type ID Space

Range Decimal Status
0x00 0 Avoid (uninitialized value)
0x01–0xFF 1–255 Available (255 type IDs)

Semantics:

  • Type ID identifies device class/type, not individual device
  • Multiple devices can share same type_id (distinguished by IΒ²C address)
  • Each type_id has independent opcode namespace

Opcode Space

Range Decimal Status
0x00 0 Convention (version info)
0x01–0xFD 1–253 Available (253 opcodes)
0xFE 254 Reserved (SET_REPLY)
0xFF 255 Convention (error response)

Opcode Allocation Convention

Purpose: Separate SET (commands) and GET (queries) operations to avoid reply ambiguity.

Common strategies: Each type_id chooses allocation based on device needs.

Strategy SET Range GET Range Use Case
Balanced 0x01-0x7F 0x80-0xFD General purpose (127 SET, 126 GET)
Sensor-heavy 0x01-0x1F 0x20-0xFD Many readings (31 SET, 222 GET)
Actuator-heavy 0x01-0xDF 0xE0-0xFD Many commands (223 SET, 30 GET)
Simple device 0x01-0x0F 0x10-0x1F Minimal opcodes (15 SET, 16 GET)

Example (balanced 0x80 split):

// LED controller
#define LED_OP_SET_ALL   0x01  // SET: Change all LEDs
#define LED_OP_BLINK     0x02  // SET: Configure blinking
#define LED_OP_GET_STATE 0x80  // GET: Query current state

Reserved Opcodes

Opcode 0xFE: SET_REPLY

The SET_REPLY command allows a controller to specify which data a peripheral should return on the next IΒ²C read request.

Wire format

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  type_id β”‚   0xFE   β”‚   0x01   β”‚ target_opcode β”‚   crc8   β”‚
β”‚ (1 byte) β”‚ (1 byte) β”‚ (1 byte) β”‚   (1 byte)    β”‚ (1 byte) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Workflow

1. Controller β†’ SET_REPLY(0x80) β†’ Peripheral  # Request opcode 0x80
2. Library intercepts, stores ctx->requested_opcode = 0x80
3. Controller β†’ IΒ²C read request β†’ Peripheral
4. Peripheral on_request() switches on ctx->requested_opcode
5. Controller ← Reply with opcode 0x80 data ← Peripheral

Properties

  • SET_REPLY is NOT dispatched to user handlers or callbacks
  • requested_opcode persists until another SET_REPLY is received
  • Initial value is 0x00 (by convention: device/version info)
  • Empty payload is ignored (no change to requested_opcode)

Opcode 0x00: Version Info Convention

By convention, opcode 0x00 should return device identification and version information.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CRUMBS_VERSION  β”‚  module_major β”‚  module_minor β”‚  module_patch β”‚
β”‚    (2 bytes)    β”‚    (1 byte)   β”‚    (1 byte)   β”‚    (1 byte)   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example implementation

void on_request(crumbs_context_t *ctx, crumbs_message_t *reply) {
    switch (ctx->requested_opcode) {
        case 0x00:  // Version info
            crumbs_msg_init(reply, MY_TYPE_ID, 0x00);
            crumbs_msg_add_u16(reply, CRUMBS_VERSION);  // Library version
            crumbs_msg_add_u8(reply, 1);  // Module major version
            crumbs_msg_add_u8(reply, 0);  // Module minor version
            crumbs_msg_add_u8(reply, 0);  // Module patch version
            break;
        // ... other opcodes ...
    }
}

Benefits:

  • Controllers can identify device types during bus scan
  • Version compatibility checking
  • Debugging aid

Versioning

Library version: CRUMBS_VERSION macro (integer: major10000 + minor100 + patch)

Module compatibility: Follow Semantic Versioning:

  • MAJOR: Incompatible protocol changes (must match)
  • MINOR: New commands added (peripheral >= controller required)
  • PATCH: Bug fixes (no compatibility impact)

Version check example:

if (crumbs_version < 1003) {  // Require >= 0.10.3
    fprintf(stderr, "CRUMBS library too old\n");
}

CRC-8 Specification

Property Value
Polynomial 0x07 (x^8 + x^2 + x + 1)
Initial 0x00
Algorithm Nibble-based (4-bit chunks)
Coverage type_id, opcode, data_len, data[]
Excluded address, crc8 field itself

Implementation: crc8_nibble_calculate() from src/crc/crc8_nibble.c


Communication Patterns

Controller β†’ [4–31 byte message] β†’ Peripheral    # Send command (SET)
Controller β†’ [SET_REPLY + target] β†’ Peripheral   # Request specific data
Controller β†’ [IΒ²C read request] β†’ Peripheral     # Read staged reply
Controller ← [4–31 byte response] ← Peripheral   # Receive data (GET)

Timing

Parameter Value Notes
Message spacing 10ms min delay() between send/read
Read timeout 50ms typical Processing time
IΒ²C clock 100kHz Use 50kHz if CRC errors
Bus length <30cm Longer needs lower clock/termination

Critical: delay(10) between send and read:

crumbs_controller_send(&ctx, 0x08, &msg, write_fn, NULL);
delay(10);
crumbs_arduino_read(NULL, 0x08, buf, sizeof(buf), 5000);

Example: Encoding Data Types

Float

crumbs_message_t msg;
crumbs_msg_init(&msg, 0x01, 0x01);
crumbs_msg_add_float(&msg, 25.5f);

Multiple values

crumbs_msg_init(&msg, 0x01, 0x02);
crumbs_msg_add_u16(&msg, 1234);    // 2 bytes
crumbs_msg_add_u8(&msg, 0xAB);     // 1 byte
crumbs_msg_add_float(&msg, 3.14f); // 4 bytes
// Total: 7 bytes payload

See Also