Skip to content

1.7 The lab kit, module roadmap, and Lab 1

Connected embedded engineering cannot be learned purely from block diagrams or simulated code. A simulator will not tell you that you forgot a 4.7 kΩ pull-up resistor on an open-drain I²C line, that a cheap USB-C cable drops your Raspberry Pi supply below 4.65 V when the Wi-Fi radio transmits, or that contact bounce on a mechanical push button fires twelve interrupts for a single finger press.

Throughout this book, every chapter pairs theoretical foundations and interactive browser laboratories with physical hardware work on a two-tier bench kit.

This book serves as the core text for EEN1071 (Connected Embedded Systems) and is structured so that by the end of its 21 chapters you can:

  1. LO1 (Landscape and state of the art): Critically evaluate state-of-the-art developments across the IoT, IIoT, and Physical AI, including multisensory perception, edge/fog/cloud partitioning, and cross-domain device collaboration.
  2. LO2 (Embedded Linux and OS architectures): Configure, administer, and program full-stack mainline Embedded Linux systems, and compare the engineering trade-offs between single-address-space RTOSes (Zephyr), monolithic kernels (Linux / PREEMPT_RT), and safety-certified microkernels (QNX).
  3. LO3 (Physical interfacing and C++ hardware abstraction): Interface digital and analogue sensors and actuators over GPIO, ADC front ends, PWM, I²C, SPI, and UART, wrapping low-level register protocols in clean, object-oriented C++ (and optional Rust) device classes.
  4. LO4 (Real-time determinism): Design and measure deterministic real-time sensing and closed-loop control systems (including fixed-point PID loops on Zephyr and low-latency interrupt handling in Linux kernel space and PREEMPT_RT).
  5. LO5 (Communications and IoT protocols): Select, configure, and benchmark appropriate application protocols (MQTT 3.1.1/5.0, CoAP, HTTP/REST) and payload serialisation formats (JSON, CBOR, Protocol Buffers).
  6. LO6 (Distributed multi-processor and wireless/fieldbus networks): Build locally connected multi-node systems combining microcontroller leaf nodes and Linux gateways over BLE, IEEE 802.15.4 / Zigbee / Thread / Matter, CAN, and LoRaWAN.
  7. LO7 (Security, regulation, and full-stack integration): Engineer secure, maintainable connected systems incorporating hardware roots of trust, mutual TLS, tamper-evident hash chains, fail-safe A/B OTA updates, local Qt 6 HMIs, and compliance with the EU Cyber Resilience Act (CRA) and Data Act.

How the five parts of this book fit together

Section titled “How the five parts of this book fit together”
PartChaptersCore engineering theme
Part I: FoundationsChapters 1–5The two-tier architecture, administering the Linux gateway (systemd, permissions, storage), C++/Rust cross-toolchains and hardware debugging, unified hardware description with Devicetree and Kconfig, and Zephyr on current microcontrollers.
Part II: Interfacing the physical worldChapters 6–10Electrical GPIO driving and libgpiod v2, Nyquist sampling and op-amp ADC front ends, board-level buses (I²C, SPI, UART, I³C), IMU sensor fusion, and device drivers (Linux Loadable Kernel Modules and Zephyr drivers).
Part III: Real-time and local softwareChapters 11–14Deterministic scheduling, priority inversion, and PID control on Zephyr RTOS; real-time Linux (PREEMPT_RT), QNX microkernels, and AMP co-processing; microamp sleep budgets; and event-driven local HMIs with Qt 6.
Part IV: NetworksChapters 15–18Device networking (IPv6, mDNS, sockets, NTP/PTP, TLS), MQTT and CoAP with local brokers and Node-RED/Home Assistant, short-range wireless (BLE, Zigbee, Thread, Matter), and wired fieldbuses (CAN, RS-485) plus LPWAN (LoRaWAN, LTE-M/NB-IoT).
Part V: Security, lifecycle, and integrationChapters 19–21Threat modelling, secure boot, mTLS, signed hash chains (and why most IoT systems do not need a blockchain), GDPR/Data Act/CRA regulation, atomic A/B OTA updates (MCUboot, RAUC/Mender), and Edge AI / TinyML partitioning.

Alongside the chapter labs, two major engineering assignments anchor the practical assessment:

  • Assignment 1, a register-level I²C real-time clock driver in C++ (Chapter 8): You will write a complete object-oriented C++ driver class (DS3231) built on top of a Linux /dev/i2c-N character-device wrapper (I2CDevice). Your driver will perform raw I²C register reads and writes using std::vector<uint8_t> buffers, convert Binary-Coded Decimal (BCD) timestamps, manipulate oscillator and status bitfields, and configure hardware alarm interrupts routed from the DS3231’s open-drain INT/SQW pin to a Raspberry Pi GPIO monitored via libgpiod v2 (with a physical snooze button).
  • Assignment 2, multi-node accelerometer telemetry, an MQTT broker and a Qt 6 supervisory HMI (Chapters 9, 14, and 15): You will interface a 3-axis digital accelerometer (ADXL345) to compute live pitch and roll angles alongside system telemetry, publish structured JSON payloads to an authenticated Mosquitto MQTT broker running on the gateway, drive a hardware three-LED tilt state machine subscriber, and build a CMake-based Qt 6 graphical dashboard (QMqttClient / Paho C++ and QCustomPlot) that plots real-time orientation and sends remote configuration commands back to the sensor node.

To complete the labs and assignments, your bench kit combines a Tier 2 Linux gateway, a Tier 1 microcontroller leaf node, and a set of digital and analogue peripherals:

  1. Tier 2 Gateway: A Raspberry Pi 5 (4 GB or 8 GB recommended, with an official 5 V / 5 A 27 W USB-C PD power supply) or Raspberry Pi 4 Model B (2 GB, 4 GB, or 8 GB, with a 5 V / 3 A USB-C supply), plus a high-endurance A2-rated microSD card (32 GB+). (Note: Legacy BeagleBone Black boards can still be used for the Linux I2C/SPI sections, though new builds target Raspberry Pi OS).
  2. Tier 1 Leaf Node: An Espressif ESP32-family development board (such as the ESP32-S3-DevKitC-1 for TinyML and USB-JTAG or the ESP32-C6-DevKitC-1 for Wi-Fi 6 and IEEE 802.15.4 / Thread), plus a data-capable USB-C cable.
  3. Core Sensors and ICs:
    • Maxim / Analog Devices DS3231 temperature-compensated I²C Real-Time Clock (RTC) breakout module (with CR2032 backup cell).
    • Analog Devices ADXL345 3-axis digital MEMS accelerometer (I²C and SPI capable).
    • Microchip MCP3208 8-channel, 12-bit SPI Analogue-to-Digital Converter (used in Chapter 7 because the Raspberry Pi has no onboard ADC), paired with a rail-to-rail operational amplifier (e.g., MCP6002) and an analogue sensor (such as a Sharp GP2Y0A21YK / GP2D12 infrared distance sensor or TMP36 temperature sensor).
  4. Passive and Switching Components: A solderless breadboard, male-to-female and male-to-male jumper wires, tactile push buttons, standard 3 mm or 5 mm LEDs, current-limiting and pull-up resistors (220 Ω, 1 kΩ, 4.7 kΩ, 10 kΩ), and an NPN transistor or small N-channel MOSFET for driving higher-current loads (the 2N7000 is adequate to about 100 mA from a 3.3 V GPIO; above that, use a part rated for 3.3 V gate drive such as the AO3400).
  5. Recommended Test Gear: A 3.3 V USB-to-TTL serial debug cable (FTDI or CP2102) and an inexpensive 8-channel 24 MHz USB logic analyser (compatible with PulseView / sigrok) for inspecting I²C, SPI, UART, and GPIO timing in Chapters 3, 6, and 7.

Section titled “Hands-on Lab 1: Headless gateway bring-up and first blink on both tiers”

In this first laboratory session, you will set up your Raspberry Pi as a headless Embedded Linux gateway (operating over the network without a dedicated monitor, keyboard, or mouse), verify its system clock and kernel environment, control its onboard status LED through the Linux sysfs virtual filesystem, and flash your first Zephyr RTOS image onto the ESP32 leaf node.

Part A: Image and pre-configure Raspberry Pi OS

Section titled “Part A: Image and pre-configure Raspberry Pi OS”

Historically, Raspbian images shipped with a hardcoded default username (pi) and password (raspberry), and headless Wi-Fi setup required manually placing a wpa_supplicant.conf file and an empty ssh file into the FAT boot partition. In April 2022, Raspberry Pi removed the default pi/raspberry credentials to eliminate default-password botnet attacks, and from Raspberry Pi OS Bookworm (Debian 12) and Trixie (Debian 13) onward, networking is managed by NetworkManager (with the boot partition mounted at /boot/firmware/).

  1. Download and install the official Raspberry Pi Imager on your laptop or workstation (Windows, macOS, or Linux).
  2. Insert your microSD card into your computer’s card reader and launch Raspberry Pi Imager:
    • Choose Device: Select your board model (Raspberry Pi 5 or Raspberry Pi 4).
    • Choose OS: Select Raspberry Pi OS (64-bit) (or Raspberry Pi OS Lite (64-bit) if you never intend to run a local desktop on the board; note that for Chapter 14 / Assignment 2 you can either use the full desktop image with WayVNC / Raspberry Pi Connect or cross-compile/forward your Qt 6 GUI).
    • Choose Storage: Select your microSD card.
  3. Click Next, and when prompted to apply OS Customisation, click Edit Settings:
    • General tab:
      • Set Hostname to a unique name on your local network (for example, rpi- followed by your name, which you then reach as <hostname>.local; avoid leaving it as raspberrypi in a shared classroom lab where thirty boards will collide). The transcripts in this book use the hostname rpi and the user derek, so substitute your own names when you type the commands.
      • Set Username and password (for example, username derek and a strong password).
      • Configure Wireless LAN with your lab Wi-Fi SSID, password, and country code (IE for Ireland) so the regulatory domain enables the correct 2.4 GHz and 5 GHz channels. (Tip: In a university lab where enterprise eduroam blocks peer-to-peer SSH, enable a Wi-Fi hotspot on your smartphone or laptop before booting the Pi and enter your hotspot’s SSID and password here).
      • Set Locale settings to Europe/Dublin and keyboard layout gb or ie.
    • Services tab:
      • Tick Enable SSH and select either Allow public-key authentication only (recommended: paste or select your ~/.ssh/id_ed25519.pub key) or Use password authentication.
  4. Save the settings and click Yes to write and verify the image. When verification completes, insert the microSD card into the unpowered Raspberry Pi and connect the USB-C power supply.

Part B: Connect headless over mDNS and SSH

Section titled “Part B: Connect headless over mDNS and SSH”

Give the Raspberry Pi 45–60 seconds on its first boot to expand the root filesystem across the microSD card, generate its SSH host keys, and associate with your Wi-Fi network.

  1. Open a terminal on your laptop (PowerShell on Windows, or Terminal on macOS/Linux) and verify that Multicast DNS (mDNS / Zeroconf / Avahi) resolves your board’s .local hostname:
Terminal window
PS C:\Users\Derek Molloy> ping rpi.local
Pinging rpi.local [fe80::2ecf:67ff:fe4a:8b12%14] with 32 bytes of data:
Reply from fe80::2ecf:67ff:fe4a:8b12%14: time=3ms
Reply from fe80::2ecf:67ff:fe4a:8b12%14: time=2ms

(Note how mDNS immediately resolves an IPv6 link-local fe80:: address or an IPv4 address without requiring you to look up DHCP leases on a router).

  1. Connect to the gateway using the built-in OpenSSH client:
Terminal window
PS C:\Users\Derek Molloy> ssh derek@rpi.local
  1. Once logged into the Raspberry Pi shell, inspect the kernel architecture, operating system release, and network time status:
Terminal window
derek@rpi:~ $ uname -a
Linux rpi 6.12.20+rpt-rpi-v8 #1 SMP PREEMPT Debian 1:6.12.20-1+rpt1 (2026-03-18) aarch64 GNU/Linux
derek@rpi:~ $ hostnamectl
Static hostname: rpi
Icon name: computer
Machine ID: 9f41c82b71e44d109c3b2048e5a17021
Chassis: embedded
Operating System: Debian GNU/Linux 13 (trixie)
Kernel: Linux 6.12.20+rpt-rpi-v8
Architecture: arm64
derek@rpi:~ $ timedatectl status
Local time: Sat 2026-11-07 19:10:42 GMT
Universal time: Sat 2026-11-07 19:10:42 UTC
RTC time: Sat 2026-11-07 19:10:42
Time zone: Europe/Dublin (GMT, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no

Part C: Control the onboard status LED via sysfs

Section titled “Part C: Control the onboard status LED via sysfs”

Before we write C++ code against the GPIO character device in Chapter 6, Linux exposes standard onboard LEDs through a kernel driver abstraction at /sys/class/leds/. Each directory inside /sys/class/leds/ represents a logical LED. ACT and PWR are managed by the kernel’s leds-gpio driver (bound from the board’s Devicetree; Chapter 4); entries such as mmc0:: are registered by other drivers, here the SD-card controller.

  1. List the available LEDs and inspect the attributes of the green activity LED (ACT):
Terminal window
derek@rpi:~ $ ls /sys/class/leds/
ACT PWR mmc0::
derek@rpi:~ $ ls -l /sys/class/leds/ACT/
-rw-rw-r-- 1 root gpio 4096 Nov 7 19:10 brightness
lrwxrwxrwx 1 root gpio 0 Nov 7 19:10 device -> ../../../leds
-r--r--r-- 1 root gpio 4096 Nov 7 19:10 max_brightness
drwxrwxr-x 2 root gpio 0 Nov 7 19:10 power
lrwxrwxrwx 1 root gpio 0 Nov 7 19:10 subsystem -> ../../../../../class/leds
-rw-rw-r-- 1 root gpio 4096 Nov 7 19:10 trigger
-rw-rw-r-- 1 root gpio 4096 Nov 7 19:10 uevent
  1. Read the trigger file. The kernel event currently controlling the LED is enclosed in square brackets ([mmc0], flashing on microSD card access):
Terminal window
derek@rpi:~ $ cat /sys/class/leds/ACT/trigger
none rc-feedback kbd-scrolllock kbd-numlock kbd-capslock kbd-kanalock kbd-shiftlock kbd-altgrlock kbd-ctrllock kbd-altlock kbd-shiftllock kbd-shiftrlock kbd-ctrlllock kbd-ctrlrlock timer oneshot heartbeat backlight gpio cpu cpu0 cpu1 cpu2 cpu3 default-on input panic actpwr [mmc0] rfkill-any rfkill-none
  1. Switch the trigger to heartbeat (which pulses the LED with a double-beat pattern whose rate increases with Linux CPU load), then switch to timer to create a custom 50 ms flasher (notice that selecting timer dynamically creates two new files, delay_on and delay_off, in the directory):
Terminal window
derek@rpi:~ $ echo heartbeat | sudo tee /sys/class/leds/ACT/trigger
heartbeat
derek@rpi:~ $ echo timer | sudo tee /sys/class/leds/ACT/trigger
timer
derek@rpi:~ $ echo 50 | sudo tee /sys/class/leds/ACT/delay_on
50
derek@rpi:~ $ echo 50 | sudo tee /sys/class/leds/ACT/delay_off
50
  1. Disable automatic triggers (none) and toggle the LED manually using brightness, then restore the default [mmc0] trigger:
Terminal window
derek@rpi:~ $ echo none | sudo tee /sys/class/leds/ACT/trigger
none
derek@rpi:~ $ echo 1 | sudo tee /sys/class/leds/ACT/brightness
1
derek@rpi:~ $ echo 0 | sudo tee /sys/class/leds/ACT/brightness
0
derek@rpi:~ $ echo mmc0 | sudo tee /sys/class/leds/ACT/trigger
mmc0

Always shut down Embedded Linux cleanly

Never unplug the USB-C power cable while Raspberry Pi OS is running. Because the Linux ext4 filesystem caches writes in RAM to reduce microSD flash wear (Chapter 2), pulling power mid-write can corrupt the filesystem journal. When finished with a session, always run:

Terminal window
derek@rpi:~ $ sudo shutdown -h now

Wait until the green ACT LED stops flashing (on a Pi 5 the power LED turns red) before disconnecting power.

Part D: Build and flash Zephyr blinky on the ESP32 leaf node

Section titled “Part D: Build and flash Zephyr blinky on the ESP32 leaf node”

Finally, verify your Tier 1 microcontroller environment. If you have not yet installed Zephyr, follow the official Zephyr Getting Started Guide (Edge Programming Section 12.5 introduces Zephyr on the ESP32-S3, but it does not cover installation). Espressif chips also need their binary radio and bootloader blobs, which Zephyr does not fetch by default:

Terminal window
(.venv) PS C:\zephyrproject\zephyr> west blobs fetch hal_espressif

The standard Zephyr blinky sample flashes whichever LED the board’s Devicetree names with the led0 alias. The ESP32-S3-DevKitC and ESP32-C6-DevKitC boards do not define one: their only on-board LED is an addressable WS2812 RGB LED, which needs a data protocol rather than a GPIO level. So you will add an external LED and describe it to Zephyr with a small Devicetree overlay. This is your first overlay; Chapter 4 explains every line of it.

Wire the LED: connect a GPIO pin through a 220 Ω resistor (from your kit) to the LED’s anode (the longer leg), and the LED’s cathode to GND. At 3.3 V this limits the current to about 6 mA. Use GPIO4 on the ESP32-S3-DevKitC-1 or GPIO2 on the ESP32-C6-DevKitC-1.

Write the overlay: create C:\een1071\lab1\esp32s3-ext-led.overlay (for the C6, use esp32c6-ext-led.overlay and change 4 to 2):

#include <zephyr/dt-bindings/gpio/gpio.h>
/ {
aliases {
led0 = &ext_led;
};
leds {
compatible = "gpio-leds";
ext_led: led_0 {
gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>;
label = "External LED";
};
};
};

Build, flash and monitor, passing the overlay to the build:

Terminal window
(.venv) PS C:\zephyrproject\zephyr> west build -p always -b esp32s3_devkitc/esp32s3/procpu samples/basic/blinky -- -DEXTRA_DTC_OVERLAY_FILE=C:/een1071/lab1/esp32s3-ext-led.overlay
(.venv) PS C:\zephyrproject\zephyr> west flash
(.venv) PS C:\zephyrproject\zephyr> west espressif monitor
--- esp_idf_monitor on COM5 115200 ---
*** Booting Zephyr OS build v4.4.0 ***
LED state: ON
LED state: OFF
LED state: ON

For the ESP32-C6, the board target is esp32c6_devkitc/esp32c6/hpcore and the overlay is esp32c6-ext-led.overlay. If the build stops at GPIO_DT_SPEC_GET(LED0_NODE, gpios), Zephyr has not found the led0 alias: check the overlay path and that you passed it after the --.

Compare what just happened across your two boards:

  • On the ESP32 leaf node, your C code was statically linked with the Zephyr RTOS kernel into a single flash image of a few hundred kilobytes, booted to main() within a fraction of a second, and toggled the GPIO pin deterministically with zero background user-space processes.
  • On the Raspberry Pi gateway, your SSH session ran as an isolated virtual-memory user-space process over a full Wi-Fi TCP/IP stack, asking the Linux kernel’s leds-gpio driver to pulse the onboard LED while systemd, NetworkManager, and systemd-timesyncd ran concurrently in the background.

In Chapter 2, we will examine how the Linux gateway boots, how systemd manages background services, how Linux permissions and groups protect hardware buses, and how to configure your gateway for reliable long-term operation.

Concept Match

Match the file in /sys/class/leds/ACT/ to its role

Quiz
Select 0/1

In Part B, ping rpi.local finds the Raspberry Pi without you looking up its address on the router. Which protocol resolves the name?

Quiz
Select 0/1

timedatectl on a Raspberry Pi 4 shows RTC time: n/a. What does this mean when the board boots without internet access?

Quiz
Select 0/1

Why does Lab 1 add a Devicetree overlay before building the Zephyr blinky sample for the ESP32-S3-DevKitC-1?