Íñigo Gutiérrez Febles
← Back to overview
Week 11

Networking and communications

in-progress

weekly schedule.

Time blockWedThuFriSatSunMonTueWed
Global class3 h
Local class1,5 h1,5 h
Research1 h2 h
Design
Fabrication
Documentation3 h
Review

overview.

Week 11 covers the full communication stack: from chip-to-chip protocols like I2C, SPI, and UART, through device-to-device networking over Wi-Fi and Bluetooth, to long-range systems like LoRa. The practical focus in Fab Academy sits between OSI Layer 1 (the physical board) and Layer 3 (the local network). The session was taught by Eric Pan, Henk Buursen, Luc Hanneuse, and Saheen Palayi.

This week connects directly to my final project: the height-adjustable standing desk uses a UART bus with software addressing — one XIAO ESP32-S3 as master and one XIAO RP2040 per leg as slave. Understanding addressing, poll cycles, and message framing is not academic for me — it is a prerequisite for the actual build.


learning objectives.

  • Demonstrate workflows used in network design.
  • Implement and interpret networking protocols and/or communication protocols.
  • Understand how software node addresses replicate the role of hardware bus addresses (I2C, CAN).

assignments.

  • Group assignment: Send a message between two projects. Documented here and on the Fab Lab León 2026 Group Page.
  • Individual assignment: Design, build, and connect a wired or wireless node with a network or bus address and a local input and/or output device.

research.

protocol overview.

The class structured communication into three layers of increasing scope:

Chip-to-chip (synchronous):

  • I2C — two wires (SDA + SCL), multi-device bus, each peripheral has a 7-bit address. Needs pull-up resistors. Best for sensors and OLEDs when wire count matters more than speed.
  • SPI — four wires (SCK, PICO, POCI, CS), one CS pin per device, up to 10 MHz+. Full-duplex. Best for SD cards, displays, or anything that needs high throughput.

Device-to-device (asynchronous):

  • UART — two wires (TX, RX), no clock line, baud rate agreed in advance. The simplest protocol. Indispensable for debugging.
  • RS-485 — differential signaling, high noise immunity, up to ~1.2 km. The industrial workhorse. My final project originally considered this before settling on CAN.
  • CAN bus — differential, multi-master capable, hardware arbitration, 11-bit or 29-bit addresses. Designed for electrically noisy environments (automotive). The target protocol for my standing desk.

Wireless:

  • BLE — 2.4 GHz, ~10–100 m, ultra-low power. GATT profile with services and characteristics. Native support on XIAO ESP32-S3.
  • Wi-Fi — high throughput, gateway to the internet. The ESP32-S3 handles both BLE and Wi-Fi on the same chip.
  • LoRa — sub-GHz (868 MHz in Europe), 2–15 km, years on a single battery. Luc Hanneuse mentioned up to 10,000 devices on a single LoRaWAN gateway. Oscar from Ayto Ponferrada had a 200 km link with a special antenna — that blew my mind.

protocol selection for the final project.

Before committing to a bus protocol, I researched what the standing desk industry actually uses. The answer was consistent across manufacturers: LIN bus (IKEA Bekant, LINAK actuators) or plain UART (Autonomous, the manufacturer of my reference desk). Both are single-master, polled architectures. Neither uses CAN. The RJ45 connectors visible in commercial desks are just a convenient multi-conductor housing — the protocol inside is UART or LIN at 9600–19200 baud, not Ethernet.

This matters because CAN was my initial assumption, inherited from the automotive framing of the problem. Re-examining it: a standing desk has one master, four slaves, low data rate (~10 bytes per poll), short distances (~2 m), no simultaneous transmission from multiple nodes, and no hard fault-tolerance requirement. CAN’s hardware arbitration and differential signaling solve problems this project does not have.

The table below compares the four realistic options for the final project bus:

CriterionUART point-to-pointUART + software addressingLIN busCAN bus
Wires between nodes2 per leg (4 pairs total)2 shared + GND1 + GND2 differential + GND
Extra hardware per nodeNoneNoneTJA1020 transceiver (~0.50 €)MCP2515 + SN65HVD230 (~5 €)
XIAO RP2040 native supportYes (UART)Yes (UART)Yes (UART + framing)No (SPI adapter needed)
PCB complexityLowLowLowHigh
AddressingImplicit (pin = leg)Software, 1 byte in frameHardware (LIN ID field)Hardware (11-bit CAN ID)
Multi-masterNoNoNoYes (not needed)
Industry precedent for desksNoYes (Autonomous)Yes (IKEA, LINAK)No
Synchronization riskHigh (no coordination)Manageable (polled)Manageable (polled)Low
Fab Academy Week 11 scopeSimpleSimpleMediumComplex

Decision: UART with software addressing (Option B). It matches the industry approach, requires zero additional hardware beyond the XIAO RP2040 boards already designed, keeps PCB fabrication simple, and validates directly in Week 11.

What “software addressing” means. Standard UART is point-to-point — one transmitter, one receiver, no concept of addresses. Hardware protocols like I2C or CAN solve multi-device communication at the silicon level: the peripheral itself filters incoming frames and only interrupts the CPU when the address matches. UART has no such mechanism. Software addressing means building that filtering in firmware. Every frame starts with a DEST_ADDR byte. Every node receives every frame on the shared bus, but the firmware immediately checks whether DEST_ADDR matches its own address — if not, it discards the frame and returns to listening. The check happens in code, not in hardware. From the application layer the result is identical to I2C addressing; it just lives one layer up.

The analogy with I2C is intentional but has limits — and explains why I2C and SPI were discarded for this bus.

I2C requires pull-up resistors on both SDA and SCL, and more importantly it is a synchronous protocol: it needs a shared clock line (SCL) in addition to the data line. That means three wires between nodes instead of two. For a desk where the bus runs through moving telescopic columns, every additional wire is a potential failure point.

SPI was discarded even faster. SPI is point-to-point by design: to address N devices you need N separate chip-select (CS) lines from the master. Four leg nodes would require four CS pins on the ESP32-S3 master and four dedicated wires — a star topology that fights the physical layout of a desk. SPI is the right choice for high-speed peripherals on the same PCB; it does not scale to a multi-node bus over metre-long cables.

UART needs only two wires (TX + RX, with shared GND). No clock line, no chip-select, no pull-up requirements. The software address layer adds the multi-node capability that UART lacks natively, at zero hardware cost.

architecture: master–slave over UART. The structure remains master–slave. The XIAO ESP32-S3 is the single master; each XIAO RP2040 leg board is a slave with a fixed 1-byte address (0x01–0x04). The master polls each slave in sequence: it sends a request frame addressed to one leg, waits for the response (distance reading + motor status), then moves to the next. No slave transmits unsolicited.

Centralisation of information. All state lives on the master. The ESP32-S3 holds the current height of each leg (from the VL53L0X readings), the target height, and the synchronisation logic. The touchscreen display connects directly to the master via I2C or SPI — it never touches the UART bus. The display shows current height, preset buttons, and any leg that is out of sync.

Synchronisation between legs. This is the main risk. Four legs driven independently will drift apart if each runs open-loop (step count only). The mitigation is closed-loop position control per leg using the VL53L0X ToF sensor: the master reads each leg’s distance every poll cycle and adjusts the motor command (speed or enable/disable) to keep all four within a tolerance band (e.g., ±2 mm). This is the same approach used in commercial desks — the anti-collision and levelling logic runs in the control box, not in the motors.

Specific risks and mitigations:

RiskCauseMitigation
Legs drift apart during movementOpen-loop stepper controlClosed-loop: master reads VL53L0X per leg each cycle, halts fast legs
One leg stalls (obstacle, mechanical binding)Motor stops but others continueMaster detects no encoder/ToF change → emergency stop all legs
UART frame lost (noise)Electrical interference from motor driversChecksum per frame; master retries once before stopping
Poll latency too high at 4 legs4 × round-trip at 9600 baudUse 115200 baud; at 6-byte frames, 4-leg cycle < 5 ms

At 115200 baud, the bus transmits 115200 bits per second. With 10 bits per byte (1 start bit + 8 data bits + 1 stop bit), that is 11,520 bytes per second, or roughly 0.087 ms per byte.

One full exchange for a single leg is 12 bytes — 6 for the master’s request frame and 6 for the slave’s response:

12 bytes × 0.087 ms/byte ≈ 1.04 ms per leg

Four legs polled in sequence:

4 × 1.04 ms ≈ 4.2 ms per full cycle

During those 4.2 ms the desk moves at 31.75 mm/s. The maximum distance one leg can travel between two consecutive corrections is:

31.75 mm/s × 0.0042 s ≈ 0.133 mm

That is the worst-case inter-leg drift: one leg has just been corrected, the next one waits a full cycle before being read. In practice the drift is smaller because the master corrects each leg on every poll, not only at the end of the cycle.

With a ±2 mm inter-leg tolerance (a conservative threshold to avoid mechanical stress on the tabletop), the safety margin is ×15. The protocol has ample headroom even accounting for processing overhead or occasional checksum retransmissions.



group assignment.

The group assignment required sending a message between two projects. Fab Lab León 2026 Group Page.

I chose Bluetooth Low Energy (BLE): it demonstrates a complete wireless node with addressing (GATT service and characteristic UUIDs define the address space), uses hardware already on hand (XIAO ESP32-S3), and the second “project” is a smartphone running nRF Connect — which the FAQ explicitly allows as a valid node.

setup.

The XIAO ESP32-S3 runs as a BLE server. It advertises a GATT service with a single readable characteristic holding a text string. The smartphone connects as a GATT client and reads the characteristic value.

Hardware: Seeed Studio XIAO ESP32-S3
IDE: Arduino IDE 2.x with the Espressif esp32 board package
App: nRF Connect (Nordic Semiconductor)
Library: BLEDevice, BLEServer, BLEUtils — included in the Espressif Arduino core, no separate installation needed

server code.

tab: BLE server — XIAO ESP32-S3 | ble-server-esp32s3.ino

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// UUIDs generated at https://www.uuidgenerator.net/
#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE server...");

  BLEDevice::init("XIAO_ESP32S3_FAB");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);

  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
    CHARACTERISTIC_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_WRITE
  );

  pCharacteristic->setValue("Hello from Fab Lab Leon!");
  pService->start();

  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);
  pAdvertising->setMaxPreferred(0x12);
  BLEDevice::startAdvertising();

  Serial.println("BLE advertising. Connect with nRF Connect and read the characteristic.");
}

void loop() {
  delay(2000);
}

tab: end

procedure.

  1. Flash the sketch to the XIAO ESP32-S3. Open the Serial Monitor at 115200 baud and confirm the “BLE advertising” message.
  2. On the smartphone, open nRF Connect → Scanner tab → Start scan.
  3. Locate XIAO_ESP32S3_FAB in the device list and tap Connect.
  4. Expand the service with UUID 4fafc201-....
  5. Tap the read icon (down arrow ↓) on the characteristic beb5483e-....
  6. The app displays the value in hex and decoded ASCII.
Arduino IDE Serial Monitor showing 'Starting BLE server... BLE advertising. Ready to connect. Phone connected.' after reset.
Serial Monitor output at 115200 baud. Three reset cycles are visible — each prints the startup sequence. The last line confirms the smartphone connected successfully.
nRF Connect Scanner showing XIAO_ESP32S3_FAB device with MAC address 34:85:18:91:40:AD at -89 dBm.
nRF Connect scanner — XIAO_ESP32S3_FAB visible and advertising. The MAC address matches the one reported by esptool during upload.
nRF Connect showing XIAO_ESP32S3_FAB connected with GATT services: Generic Attribute (0x1801) and Generic Access (0x1800).
Connection established — nRF Connect displays the GATT service tree. Scrolling down reveals the custom application service with UUID 4fafc201-...
nRF Connect Client view showing Unknown Service UUID 4fafc201- and Unknown Characteristic UUID beb5483e-, Properties READ WRITE, while disconnected.
Client view showing the custom service and characteristic UUIDs. The board had disconnected — pressing reset on the XIAO restarted advertising and allowed reconnection.
nRF Connect showing the Unknown Characteristic value 'Every day is another chance to ignite!!' read from the XIAO ESP32-S3.
Characteristic value read successfully: "Every day is another chance to ignite!!" The hex bytes and decoded ASCII confirm the message was transmitted correctly via BLE.

what I learned.

BLE addressing works at two levels: the MAC address of the device (handled automatically by the stack) and the GATT hierarchy of service UUID → characteristic UUID. The UUID is how the client selects exactly which data to read. When the assignment says “network or bus address,” in BLE this maps to the characteristic UUID.

The setMinPreferred(0x06) and setMaxPreferred(0x12) calls address a known compatibility issue with iPhone BLE connections. Without them, iOS sometimes fails to complete GATT discovery.



individual assignment.

plan and hardware.

The individual assignment requires a node with a network or bus address and a local input or output device. I am reusing boards from previous weeks — the FAQ explicitly permits this.

Three nodes share a single UART bus. Two nodes is enough to demonstrate request/response addressing, but not enough to prove the bus is truly shared — with two nodes it could just be point-to-point UART with address labels painted on. Three nodes make it unambiguous: each node must actively ignore frames addressed to the other two.

Nodes:

  • Node 0 — Master: XIAO RP2040 (my general-purpose breakboard). Drives the timing loop and sends LED commands to both slaves.
  • Node 1 — Slave A: XIAO RP2040 on the QPAD board, designed by Quentin Bolsee and used in Week 04. Local output: red LED.
  • Node 2 — Slave B: XIAO ESP32-S3 (from the group assignment). Local output: green LED.

Protocol: UART, 115200 baud
Addressing: 1-byte software node address in each frame header

Demo — distributed traffic light. The master runs a fixed 2-second cycle. On the first half it sends a LED-ON frame to node 1 (red) and a LED-OFF frame to node 2 (green). On the second half it reverses: LED-OFF to node 1, LED-ON to node 2. The two LEDs are never on at the same time.

Both command frames travel on the same shared physical bus. Node 1 executes only the frame addressed to 0x01 and ignores the one addressed to 0x02. Node 2 does the opposite. The Serial Monitor on the master prints the DEST address of every frame sent, making the selective addressing explicitly visible in software.

The demo requires no sensor — the timing logic lives entirely on the master. This keeps the hardware setup minimal and the bus behaviour easy to photograph and explain.

I chose UART because it is the simplest possible foundation for testing a request/response addressing scheme with software addressing — the same architecture the final project uses.

addressing scheme.

Each node has a fixed 1-byte address defined in firmware:

Node 0 — Master (XIAO RP2040):        0x00
Node 1 — Slave A (XIAO RP2040/QPAD):  0x01
Node 2 — Slave B (XIAO ESP32-S3):     0x02

Frame format (6 bytes):

Byte 0: START    — 0xFF (frame delimiter)
Byte 1: DEST     — destination address
Byte 2: SRC      — source address
Byte 3: CMD      — command
Byte 4: PAYLOAD  — data byte (0x00 if unused)
Byte 5: CHECKSUM — XOR of bytes 1–4

Commands:

0x03 — LED command (master → node 1 or node 2; payload: 0x00 = off, 0x01 = on)

A node discards any frame where DEST does not match its own address — see the software addressing explanation in the research section above.

wiring.

All three nodes share the same two wires: TX from the master connects to RX on both slaves, and both slave TX lines connect back to RX on the master. A common GND ties everything together.

Node 0 — Master         Node 1 — Slave A        Node 2 — Slave B
XIAO RP2040             XIAO RP2040 / QPAD       XIAO ESP32-S3

GPIO0 / TX1  ───────────  GPIO1 / RX1
             ───────────  GPIO44 / RX  (ESP32-S3)

GPIO1 / RX1  ←──────────  GPIO0 / TX1
             ←──────────  GPIO43 / TX  (ESP32-S3)

GND          ───────────  GND          ──────────  GND

The QPAD board exposes the XIAO RP2040 header pins directly. The XIAO ESP32-S3 uses Serial1 mapped to GPIO43 (TX) / GPIO44 (RX) in the Espressif Arduino core.

The VL53L0X breakboard connects to node 1 via I2C:

VL53L0X     XIAO RP2040 (Node 1 / QPAD)
SDA      →  GPIO6 / SDA
SCL      →  GPIO7 / SCL
VCC      →  3.3V
GND      →  GND

Red LED connects to node 1 via a 220Ω resistor to GPIO26. Green LED connects to node 2 via a 220Ω resistor to GPIO2.

Three boards connected on a shared UART bus: XIAO RP2040 master, XIAO RP2040 on QPAD with VL53L0X and red LED, and XIAO ESP32-S3 with green LED.
Three-node UART bus. All TX/RX lines connect in parallel — the shared bus is visible as the two wires that branch to all three boards.

code.

[Sketches to be added after hardware validation.]

testing.

[Logic analyzer captures and Serial Monitor output to be added after hardware test.]


problems and solutions.

[To be filled in as issues arise during the week.]


files and resources.

FileDescription
ble-server-esp32s3.inoBLE server sketch for XIAO ESP32-S3

References:



conclusions.