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:
addressfield 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, anddata[](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_opcodepersists 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.
Recommended payload format (5 bytes)
βββββββββββββββββββ¬ββββββββββββββββ¬ββββββββββββββββ¬ββββββββββββββββ
β 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
- api-reference.md - Complete API documentation including message helpers