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
Sketches inclosos en tots els board packages — disponibles a File → Examples → IoTVertebrae.
Demostra totes les operacions d'E/S digitals. Configura una vèrtebra, llegeix entrades síncronament i escriu sortides.
Demostra totes les operacions d'E/S analògiques, síncrones i no bloquejants via memòria interna.
Llegeix i escriu simultàniament els quatre canals ADC/DAC d'una vèrtebra analògica. Útil per a proves de totes les entrades i sortides analògiques a la vegada.
Demostració completa de tota la API. Ambdues vèrtebres a la mateixa adreça. Inclou callback async de canvis digitals.
Firmware complet per al cap02 (ESP32-S3) amb connectivitat MQTT. Inclou blocs de compilació condicional #ifdef IOTV_QEMU_BUILD per adaptar-se automàticament al simulador QEMU o al maquinari real.
#ifdef IOTV_QEMU_BUILD — codi específic de simulacióVersió mínima del firmware Head02. Arrenca el bus CAN / MQTT i alimenta les vèrtebres i costelles. És la base recomanada per a projectes nous que comencen des del cap.
Firmware complet per al cap01 (ESP32 legacy) amb connectivitat MQTT via bus I2C. Equivalent a Head02 però per al maquinari de la primera generació.
Versió mínima del firmware Head01. Alimenta les vèrtebres i costelles i cedeix el control a la Raspberry Pi via I2C o CAN. Firmware recomanat per a instal·lacions head01 en producció.
IOTV_QEMU_BUILD definida pel board package QEMU). Dins d'aquest bloc, el WiFi es redirigeix a Ethernet emulat i el bus CAN es substitueix per un shim MQTT sobre TCP. El mateix sketch compila i funciona tant al maquinari real com al simulador sense modificacions.
begin() gestiona automàticament el power-on. No cal cap pinMode / delay manual a setup(). Feu servir begin(tx, rx, bitrate, false) per ometre el power-on si el bus ja està alimentat.
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);
Overload of din() that also reports whether a valid reply arrived. Sets *ok = true if the vertebra responded within 500 ms, *ok = false on timeout (returned value is 0 and meaningless). Pass nullptr to behave identically to the two-argument version.
bool ok; uint8_t v = iotv.din(addr, SIDE_A, &ok); if (!ok) Serial.println("timeout");
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.
Reads all four analog channels of one side in a single CAN request (one RTR + one reply). Faster than calling ainv() four times. Fills out[0..3] with channels 1–4 in volts. Also updates internal memory so subsequent iainv() calls reflect the values. Returns true on success; false on timeout (out is left untouched).
float ch[4]; if (iotv.ainv4(addr, SIDE_A, ch)) { Serial.printf("ch1=%.2f ch2=%.2f\n", ch[0], ch[1]); }
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);
Returns true if at least one analog reading has been received for the given address and side (via ainv(), ainv4(), or the async dispatcher). Allows distinguishing "no data yet" from a genuine 0.0 V reading, avoiding spurious publications at startup.
if (iotv.aMemValid(addr, SIDE_A)) { float v = iotv.iainv(addr, SIDE_A, 1); // guaranteed to be a real reading }
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.