This guide covers installation, configuration, and first-run setup for CRUMBS on all supported platforms: Arduino, PlatformIO, and Linux.
Platform: PlatformIO (recommended), Arduino IDE (simple projects), or Linux (Raspberry Pi/PC)
Hardware: 4.7kΩ pull-ups on SDA/SCL, common ground. Level shifter for 5V↔3.3V.
- Open Arduino IDE
- Go to Sketch → Include Library → Manage Libraries
- Search for "CRUMBS"
- Click Install
- Download or clone the CRUMBS repository
- Place the
CRUMBSfolder in your Arduinolibrariesdirectory- Windows:
Documents\Arduino\libraries\ - macOS:
~/Documents/Arduino/libraries/ - Linux:
~/Arduino/libraries/
- Windows:
- Restart Arduino IDE
Create a new sketch:
#include <crumbs_arduino.h>
#include <crumbs_message_helpers.h>
#define I2C_ADDRESS 0x08
crumbs_context_t ctx;
static uint8_t g_led_state = 0;
static void reply_version(crumbs_context_t *ctx, crumbs_message_t *reply, void *u)
{
(void)ctx; (void)u;
crumbs_build_version_reply(reply, 0x01, 1, 0, 0); // type_id, major, minor, patch
}
static void reply_get_state(crumbs_context_t *ctx, crumbs_message_t *reply, void *u)
{
(void)ctx; (void)u;
crumbs_msg_init(reply, 0x01, 0x80);
crumbs_msg_add_u8(reply, g_led_state);
}
void on_message(crumbs_context_t *ctx, const crumbs_message_t *msg)
{
if (msg->opcode == 0x01 && msg->data_len >= 1) {
g_led_state = msg->data[0];
digitalWrite(LED_BUILTIN, g_led_state ? HIGH : LOW);
}
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
crumbs_arduino_init_peripheral(&ctx, I2C_ADDRESS);
crumbs_set_callbacks(&ctx, on_message, NULL, NULL);
crumbs_register_reply_handler(&ctx, 0x00, reply_version, NULL); // version query
crumbs_register_reply_handler(&ctx, 0x80, reply_get_state, NULL); // GET state
}
void loop() {
// Wire callbacks handle everything
}Upload: Select board/port, upload, wire I²C + GND + pull-ups.
Always delay 10 ms between send/read:
crumbs_controller_send(&ctx, 0x08, &msg, crumbs_arduino_wire_write, NULL);
delay(10);
crumbs_message_t reply;
crumbs_controller_read(&ctx, 0x08, &reply, crumbs_arduino_read, NULL);#include <crumbs_arduino.h>
#include <crumbs_message_helpers.h>
crumbs_context_t ctx;
void setup() {
Serial.begin(9600);
crumbs_arduino_init_controller(&ctx);
// Send LED ON command to peripheral at 0x08
crumbs_message_t msg;
crumbs_msg_init(&msg, 0x01, 0x01); // type=1, opcode=1
crumbs_msg_add_u8(&msg, 1); // payload: LED ON
int rc = crumbs_controller_send(&ctx, 0x08, &msg,
crumbs_arduino_wire_write, NULL);
Serial.print("Send result: ");
Serial.println(rc); // 0 = success
}
void loop() {
delay(1000);
}| Issue | Solution |
|---|---|
| Compile error: "crumbs.h not found" | Verify library in libraries/ folder, restart IDE |
| No response from peripheral | Check wiring, verify addresses, measure 3.3V on SDA/SCL with pull-ups |
| Data corruption | Add Wire.setClock(100000) to slow clock, add delays between messages |
| Multiple definitions error | Include CRUMBS headers in only one file, or use header guards |
- Arduino Uno/Nano: A4 (SDA), A5 (SCL)
- Arduino Mega: 20 (SDA), 21 (SCL)
- Arduino Due: 20 (SDA), 21 (SCL) — 3.3V logic
- ESP32: GPIO 21 (SDA), GPIO 22 (SCL) — configurable
- ESP8266: GPIO 4 (SDA), GPIO 5 (SCL)
- Required on SDA and SCL lines
- Typical value: 4.7kΩ to Vcc (3.3V or 5V depending on board)
- Many boards have built-in pull-ups (may need external ones for longer wires)
PlatformIO can automatically fetch CRUMBS as a library dependency.
Example platformio.ini (Arduino Nano):
[env:nanoatmega328new]
platform = atmelavr
board = nanoatmega328new
framework = arduino
lib_deps =
cameronbrooks11/CRUMBS@^0.11.0
build_flags =
-DCRUMBS_MAX_HANDLERS=8 ; optional: reduce handler table sizeExample platformio.ini (ESP32):
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
cameronbrooks11/CRUMBS@^0.11.0
build_flags =
-DCRUMBS_MAX_HANDLERS=8 ; optionalESP32 I²C pins: GPIO 21 (SDA), GPIO 22 (SCL) by default. Use
Wire.begin(sda, scl)to override.
To install and build:
pio lib install
pio runCreate src/main.cpp with the same Arduino code shown above. PlatformIO will automatically:
- Download CRUMBS library
- Compile with correct build flags
- Link everything together
To adjust handler memory usage:
build_flags =
-DCRUMBS_MAX_HANDLERS=4 # Reduce from default 16For multiple environments:
[env:controller]
board = uno
build_flags =
-DROLE_CONTROLLER
[env:peripheral]
board = nano
build_flags =
-DROLE_PERIPHERAL
-DI2C_ADDRESS=0x08| Issue | Solution |
|---|---|
| Library not found | Run pio lib install, check internet connection |
| Handler table full | Set -DCRUMBS_MAX_HANDLERS=<smaller number> |
| ABI mismatch error | Ensure CRUMBS_MAX_HANDLERS set in build_flags, not in code |
| Upload fails | Check USB port permissions, verify board selection |
Note: Linux HAL supports controller mode only. Peripheral mode (I²C target) is not implemented. For peripheral devices, use Arduino or other microcontroller.
# Ubuntu/Debian
sudo apt-get install build-essential cmake git
# Raspberry Pi OS
sudo apt-get install cmake git
# Fedora/RHEL
sudo dnf install cmake gcc gitCRUMBS Linux HAL requires the linux-wire library for I²C bus access.
# Clone linux-wire
git clone https://github.com/FEASTorg/linux-wire.git
cd linux-wire
# Build and install
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
sudo cmake --install build --prefix /usr/localTo verify installation:
cmake --find-package -DNAME=linux_wire -DCOMPILER_ID=GNU \
-DLANGUAGE=C -DMODE=EXIST
# Should print: "linux_wire found."For development without system install:
# Build linux-wire locally
cd linux-wire
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
# Point CRUMBS at local build
cd ../CRUMBS
cmake -S . -B build -DCRUMBS_ENABLE_LINUX_HAL=ON \
-DCMAKE_PREFIX_PATH=$HOME/linux-wire/build
cmake --build build --parallelThe canonical CMake dependency contract for Linux HAL consumers is the namespaced target linux_wire::linux_wire.
Supported integration modes:
- Installed package:
find_package(linux_wire CONFIG REQUIRED)provideslinux_wire::linux_wiredirectly. - Sibling source ingestion: add
linux-wirebeforeCRUMBSin the parent build. If a raw build-tree target namedlinux_wirealready exists,CRUMBScreates a local bridge target namedlinux_wire::linux_wireand links against that.
This preserves one canonical target name for CRUMBS while still supporting active multi-repo development.
Parent-build example:
cmake_minimum_required(VERSION 3.13)
project(my_linux_stack C CXX)
set(LINUX_WIRE_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(CRUMBS_ENABLE_LINUX_HAL ON CACHE BOOL "" FORCE)
set(CRUMBS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(CRUMBS_ENABLE_TESTS OFF CACHE BOOL "" FORCE)
add_subdirectory(../linux-wire linux_wire_subbuild)
add_subdirectory(../CRUMBS crumbs_subbuild)Avoid linking exported or installable CRUMBS targets directly to a raw linux_wire target. That breaks CMake export generation. The bridge or installed-package path is the stable pattern.
# Clone CRUMBS
git clone https://github.com/FEASTorg/CRUMBS.git
cd CRUMBS
# Configure and build (Linux preset enables the HAL and examples)
cmake --preset linux
cmake --build --preset linux
# Optional: system-wide install
sudo cmake --install build-linux --prefix /usr/localLinux I²C devices (/dev/i2c-*) typically require root access or group membership.
sudo usermod -a -G i2c $USER
# Log out and back in for changes to take effectsudo ./build/crumbs_simple_linux_controllerCreate /etc/udev/rules.d/99-i2c.rules:
KERNEL=="i2c-[0-9]*", GROUP="i2c", MODE="0660"
Then reload rules:
sudo udevadm control --reload-rules
sudo udevadm trigger# Enable I²C interface
sudo raspi-config
# Navigate to: Interfacing Options → I2C → Enable
# Verify I²C device exists
ls -l /dev/i2c-*
# Should show /dev/i2c-1 (or /dev/i2c-0 on older models)
# Load kernel module (if needed)
sudo modprobe i2c-devThe example programs are built automatically when CRUMBS_BUILD_EXAMPLES=ON.
Run simple controller:
sudo ./build/crumbs_simple_linux_controller /dev/i2c-1 0x08Scan for CRUMBS devices:
# Non-strict mode (attempt reads, send probe if needed)
sudo ./build/crumbs_simple_linux_controller scan
# Strict mode (read-only checks, safer for sensitive devices)
sudo ./build/crumbs_simple_linux_controller scan strictExample CMake project structure:
my_project/
├── CMakeLists.txt
└── src/
└── main.c
CMakeLists.txt:
cmake_minimum_required(VERSION 3.13)
project(my_controller C)
find_package(crumbs CONFIG REQUIRED)
add_executable(my_controller src/main.c)
target_link_libraries(my_controller PRIVATE crumbs::crumbs)src/main.c:
#include "crumbs.h"
#include "crumbs_linux.h"
#include "crumbs_message_helpers.h"
#include <stdio.h>
int main() {
crumbs_context_t ctx;
crumbs_linux_i2c_t bus;
// Open I²C bus
int rc = crumbs_linux_init_controller(&ctx, &bus, "/dev/i2c-1", 10000);
if (rc != 0) {
fprintf(stderr, "Failed to open I2C bus: %d\n", rc);
return 1;
}
// Send message
crumbs_message_t msg;
crumbs_msg_init(&msg, 0x01, 0x01);
crumbs_msg_add_u8(&msg, 1); // LED ON
rc = crumbs_controller_send(&ctx, 0x08, &msg,
crumbs_linux_i2c_write, &bus);
printf("Send result: %d\n", rc);
crumbs_linux_close(&bus);
return 0;
}Build:
cmake -S . -B build
cmake --build build
./build/my_controller| Issue | Solution |
|---|---|
linux_wire not found |
Install linux-wire (see above), or use -DCMAKE_PREFIX_PATH |
/dev/i2c-1 not found |
Enable I²C in raspi-config, load i2c-dev module |
| Permission denied | Add user to i2c group or use sudo |
i2c-dev module not loaded |
Run sudo modprobe i2c-dev, add to /etc/modules for boot |
| Link errors | Verify crumbs::crumbs target linked in CMake |
| Wrong I²C bus | Use i2cdetect -l to list buses, adjust device path |
Controller Peripheral
--------- ----------
SDA ─────────── SDA
SCL ─────────── SCL
GND ─────────── GND
| |
├── 4.7kΩ ── Vcc (pull-up)
└── 4.7kΩ ── Vcc (pull-up)
Controller Peripheral 1 (0x08) Peripheral 2 (0x09)
--------- ------------------ ------------------
SDA ────┬────── SDA SDA
│
SCL ────┼──┬─── SCL SCL
│ │
GND ────┼──┼─── GND GND
│ │
4.7kΩ │ └── 4.7kΩ to Vcc
│
└────── Vcc (3.3V or 5V)
Important:
- All devices must share a common ground
- Only one set of pull-up resistors needed per bus
- Use 3.3V logic if any device is 3.3V-only
- Maximum bus length: ~1 meter (longer requires lower pull-up resistance)
Use the built-in Wire scanner to verify devices are visible:
#include <Wire.h>
void setup() {
Serial.begin(9600);
Wire.begin();
Serial.println("Scanning I2C bus...");
for (uint8_t addr = 0x08; addr < 0x78; addr++) {
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
Serial.print("Found device at 0x");
Serial.println(addr, HEX);
}
}
}
void loop() {}Verify devices speak CRUMBS protocol:
#include <crumbs_arduino.h>
crumbs_context_t ctx;
void setup() {
Serial.begin(9600);
crumbs_arduino_init_controller(&ctx);
uint8_t found[32];
int count = crumbs_controller_scan_for_crumbs(
&ctx, 0x08, 0x77, 1, // strict mode
crumbs_arduino_wire_write, crumbs_arduino_read, NULL,
found, 32, 50000);
Serial.print("Found ");
Serial.print(count);
Serial.println(" CRUMBS devices:");
for (int i = 0; i < count; i++) {
Serial.print(" 0x");
Serial.println(found[i], HEX);
}
}
void loop() {}# List I²C buses
i2cdetect -l
# Scan bus 1 for devices
i2cdetect -y 1# Non-strict mode
sudo ./build/crumbs_simple_linux_controller scan
# Strict mode (safer, read-only)
sudo ./build/crumbs_simple_linux_controller scan strictOnce your platform is set up and basic communication is working:
-
Work through examples — Progressive tutorials in
examples/core_usage/- Start with
hello_peripheral/+hello_controller/(Arduino) - Or
simple_controller/(Linux)
- Start with
-
Learn handler dispatch — Register per-opcode handlers instead of switch statements
- SET ops:
crumbs_register_handler()— seeexamples/handlers_usage/ - GET ops:
crumbs_register_reply_handler()— seeexamples/families_usage/lhwit_family/ - Read API Reference: Handler Dispatch
- SET ops:
-
Use message helpers — Type-safe payload builders
- Include
crumbs_message_helpers.h - See API Reference: Message Helpers
- Include
-
Create command headers — Reusable command definitions
- See
examples/handlers_usage/mock_ops.h
- See
-
Optimize memory — Reduce handler table size for constrained devices
- Set
CRUMBS_MAX_HANDLERSvia build flags
- Set
- API Reference — Complete function documentation
- Protocol Specification — Wire format and versioning
- Examples — Working code for all platforms
- Architecture — Design decisions and internals