IoT-Vertebrae is an Arduino library for ESP32 platforms that provides a clean API to communicate with modular digital and analog I/O nodes over a CAN bus.
Add the board URLs to Arduino IDE, then install the packages.
For boards based on ESP32-S3-WROOM (IDF 5.1). CAN TX: GPIO17 / RX: GPIO18.
For boards based on ESP32-WROOM-32D (IDF 4.4). CAN TX: GPIO27 / RX: GPIO26.
esp32_board_0 version 2.0.17 (required for Legacy)esp32_board_1 version 3.0.7 (required for ESP32-S3)IoTVertebrae (ESP32-S3) and/or IoTVertebrae-legacy (ESP32)# Add all index URLs (once) arduino-cli config add board_manager.additional_urls \ https://raw.githubusercontent.com/vishalsoniindia/Multi_ESP32_Package/refs/heads/main/package_multi_esp32_index.json arduino-cli config add board_manager.additional_urls \ https://iotv.binefa.cat/arduino/iot-vertebrae/package_iotv_index.json arduino-cli config add board_manager.additional_urls \ https://iotv.binefa.cat/arduino/iot-vertebrae-legacy/package_iotv_legacy_index.json # Update index and install base packages first arduino-cli core update-index arduino-cli core install esp32_board_0:esp32@2.0.17 # required for Legacy arduino-cli core install esp32_board_1:esp32@3.0.7 # required for S3 arduino-cli core install IoTVertebrae:esp32 # S3 arduino-cli core install IoTVertebrae-legacy:esp32 # Legacy # Compile arduino-cli compile --fqbn IoTVertebrae:esp32:iot_vertebrae MySketch.ino
esp32_board_0 version 2.0.17 (required for Legacy)esp32_board_1 version 3.0.7 (required for ESP32-S3)IoTVertebrae (ESP32-S3) and/or IoTVertebrae-legacy (ESP32)# Run from the folder where the script was downloaded:
powershell -ExecutionPolicy Bypass -File .\install-iotv-windows.ps1
The script installs arduino-cli to C:\Ar with a short path to avoid the Windows filename length limit, and sets up both IoTVertebrae board packages.
# Once installed by the script, compile with: $CLI = "C:\Users\$env:USERNAME\AppData\Local\arduino-cli\arduino-cli.exe" $CFG = "C:\Ar\arduino-cli.yaml" # ESP32-S3 & $CLI --config-file $CFG compile ` --fqbn "IoTVertebrae:esp32:iot_vertebrae" sketch.ino # ESP32 Legacy & $CLI --config-file $CFG compile ` --fqbn "IoTVertebrae-legacy:esp32:iot_vertebrae_legacy" sketch.ino # Upload (replace COM3 with the correct port) & $CLI --config-file $CFG upload ` --fqbn "IoTVertebrae-legacy:esp32:iot_vertebrae_legacy" ` --port COM3 sketch.ino
Three ready-to-upload sketches included in both packages.
Demonstrates all digital I/O operations. Configure a vertebra, read inputs synchronously, and write outputs.
Demonstrates all analog I/O operations, both synchronous and non-blocking via internal memory.
Complete demonstration of the entire API. Both digital and analog vertebrae at the same address. Includes async digital change callback.
begin() automatically handles the power-on sequence (GPIO raise + stabilization delays). No manual pinMode / delay needed in setup(). Use begin(tx, rx, bitrate, false) to skip power-on if the bus is already powered.
Global instance: iotv — include with #include <IoTVertebrae.h>
Initializes the CAN bus using the default pins and bitrate defined in the board package. Automatically performs the power-on sequence (raises 3.3V rail, then 24V rail, waits for stabilization). Returns true on success.
if (!iotv.begin()) { Serial.println("CAN error"); while (true); }
Full overload. Specify custom CAN pins and bitrate. Set doPowerOn = false to skip the power-on sequence (useful when the bus is already powered or in lab bench setups).
| param | type | description |
|---|---|---|
| txPin | int | CAN TX GPIO number |
| rxPin | int | CAN RX GPIO number |
| bitrate | uint32_t | 100000 / 250000 / 500000 bps |
| doPowerOn | bool | true = perform power-on sequence |
iotv.begin(17, 18, 100000, false); // skip power-on
Stops the TWAI driver, deletes FreeRTOS queues and dispatcher task, then powers off (24V first, then 3.3V).
Converts a 4-bit binary string to an address byte (0–15).
uint8_t a = iotv.addr("0101"); // → 5
Configures the operating mode of both sides of a digital vertebra. Sends one or two CAN frames as required by the protocol.
| param | type | values |
|---|---|---|
| addr | uint8_t | 0–15 |
| modeA / modeB | IotvDigMode | DIN, DOUT, PWM, TOUCH, NONE |
iotv.dsetup(addr, DIN, DOUT); // A=input, B=output iotv.dsetup(addr, PWM, DIN); // A=PWM output, B=input
Reads the firmware version string from a digital vertebra (e.g. "1.5"). Returns "" on timeout.
Reads the current configuration from the vertebra and returns a human-readable string (e.g. "A:din, B:dout").
Sends an RTR request and waits up to 500 ms for the vertebra to reply with the 8-bit input state (active-high corrected). Returns 0 on timeout. Also updates the internal memory so a subsequent idin() reflects this read.
uint8_t v = iotv.din(addr, SIDE_A);
Returns the last digital input value stored in internal memory by the dispatcher. Does not send any CAN frame — safe to call every loop iteration. Returns 0 if no data has been received yet for this address.
Returns a single bit (0 or 1) from the internal digital memory. bit is 0–7 (LSB first).
uint8_t b = iotv.idinbit(addr, SIDE_A, 3); // bit 3
Writes a full byte to a digital output side. All 8 outputs are updated in a single CAN frame.
iotv.dout(addr, SIDE_B, 0b00001111); // bits 0-3 ON
Writes a single output bit without affecting the others. bit is 0–7, val is 0 or 1.
iotv.doutbit(addr, SIDE_B, 2, 1); // set bit 2
Writes a PWM duty cycle (0–255) to a single output bit on a side configured as PWM. The side must have been set to PWM mode via dsetup().
Registers a callback invoked by the internal dispatcher (core 0) whenever a spontaneous digital change notification is received. Do not call Serial or other non-thread-safe functions inside the callback. Use a volatile flag and process it in loop().
volatile bool changed = false; volatile uint8_t lastA, lastB; void onChange(uint8_t addr, uint8_t a, uint8_t b) { lastA = a; lastB = b; changed = true; // no Serial here! } iotv.onDinChange(onChange);
Sends an RTR request and returns the ADC reading in volts (range −10.0 to +10.0 V, 2 decimal places). Waits up to 500 ms. Note: ADC stabilizes ~330 ms after a DAC write; the first read after power-on may return −10.00 V.
float v = iotv.ainv(addr, SIDE_A, 1); // channel 1
Same as ainv() but returns the raw 16-bit ADC value (0–26624). Use ain2v() to convert.
Returns the last analog value (in volts) stored in internal memory by the async dispatcher. Requires setAinFreq() to be called first. Returns 0.0 if no data yet. Safe to call every loop iteration.
// In setup: iotv.setAinFreq(addr, 200); // 200 ms period // In loop: float v = iotv.iainv(addr, SIDE_A, 1);
Commands the analog vertebra to start pushing ADC readings autonomously every periodMs milliseconds. The dispatcher (running on core 0) receives these frames and updates internal memory. Pass periodMs = 0 to disable. Recommended: 50–500 ms.
Writes a voltage (0.0–10.0 V) to a DAC channel. The library converts to the 12-bit raw value (0–4095) and clamps to avoid accidental EEPROM writes on the vertebra firmware. ADC stabilization takes ~330 ms after a write.
iotv.aoutv(addr, SIDE_B, 1, 5.0f); // 5V on ch1
Raw DAC write. val is 0–4095 (0 V to 10 V). Use v2aout() to convert from volts.
Returns the analog vertebra firmware version string (e.g. "1.5").
Returns the analog vertebra side configuration as a string (e.g. "A:ain, B:aout").
Converts raw ADC (0–26624) to voltage (−10.0 to +10.0 V, 2 decimal places). Formula: ((20×raw)/26624)−10.
Converts voltage (0.0–10.0 V) to raw DAC value (0–4095), clamped to avoid out-of-range values.
SIDE_A = 0 // rib connector A SIDE_B = 1 // rib connector B
DIN = 0 // digital input DOUT = 1 // digital output PWM = 2 // PWM output (one side only) TOUCH = 3 // touch input (side B only) NONE = 4 // no rib connected
IOTV_DELAY_3V3_MS 1000 // wait after 3.3V on IOTV_DELAY_24V_MS 1000 // wait after 24V on IOTV_DELAY_BUS_MS 200 // CAN bus stabilization
Compile and run IoT-Vertebrae sketches in a web simulator — no hardware needed.
Board package for the web simulator iotvSim.binefa.cat. Firmware runs inside QEMU and communicates with SVG digital twins via MQTT.
The student compiles with the QEMU board, uploads the binary ZIP to the simulator, and QEMU runs the firmware. Physical vertebrae are replaced by interactive SVG twins in the browser.
esp32 by Espressif Systems version 3.0.7 must be installed first (same as for ESP32-S3 boards).
#include <IoTVertebrae.h>. Compile with Sketch → Export Compiled Binary (Ctrl+Alt+S).build/ folder) to iotvSim.binefa.cat. Click ▶ Start QEMU and watch the digital twins in action.