Serial Bus Interfaces: I2C, SPI, I2S
This reference covers the three serial buses used throughout the course — their electrical characteristics, protocol details, Linux kernel subsystems, Device Tree configuration, and common debugging techniques.
1. At a Glance
| I2C | SPI | I2S | |
|---|---|---|---|
| Full name | Inter-Integrated Circuit | Serial Peripheral Interface | Inter-IC Sound |
| Signals | SDA, SCL (2 wires) | MOSI, MISO, SCLK, CS (4+ wires) | BCLK, LRCLK, DOUT (3+ wires) |
| Direction | Half-duplex | Full-duplex | Simplex (per data line) |
| Speed | 100 kHz – 3.4 MHz | 1 MHz – 100+ MHz | Determined by sample rate |
| Addressing | 7-bit address on bus | Chip select per device | Left/Right channel clock |
| Multi-device | Yes (shared bus, addresses) | Yes (one CS per device) | Yes (TDM slots) |
| Typical use | Sensors, EEPROMs, RTCs | Displays, flash, ADCs, IMUs | Microphones, DACs, codecs |
| Pull-ups needed | Yes (SDA + SCL) | No | No |
| Course tutorials | MCP9808 Driver | BMI160 SPI, SPI Display | I2S Audio Viz |
When to Choose Which
- I2C when you have multiple slow sensors and want minimal wiring (2 wires for all devices)
- SPI when you need speed (displays, IMUs at high sample rates, flash memory)
- I2S when you need audio (microphones, DACs, audio codecs)
2. I2C — Inter-Integrated Circuit
2.1 Electrical Layer
I2C uses two open-drain lines with external pull-up resistors:
VCC (3.3V)
│ │
├──┤4.7kΩ├──┬──── SDA (data)
│ │ │
├──┤4.7kΩ├──┼──── SCL (clock)
│ │
│ ┌──────┴──────┐ ┌──────────────┐
│ │ Master (Pi) │ │ Slave (sensor)│
│ │ SDA SCL │ │ SDA SCL │
│ └─────────────┘ └──────────────┘
│
GND ─────────────────────────────────────
Open-drain means devices can only pull the line LOW. The pull-up resistor returns it to HIGH. This allows multi-master arbitration — if two devices drive simultaneously, both see the combined result.
Pull-up resistor sizing: Too high → slow rise time (fails at high speed). Too low → excessive current when driving low. Rule of thumb:
| Speed | Capacitance | Resistor |
|---|---|---|
| 100 kHz (standard) | < 400 pF | 4.7 kΩ |
| 400 kHz (fast) | < 400 pF | 2.2 kΩ |
| 1 MHz (fast-plus) | < 550 pF | 1 kΩ |
The Raspberry Pi has built-in 1.8 kΩ pull-ups on the I2C pins (GPIO 2/3). For most sensors, no external pull-ups are needed.
2.2 Protocol
Every I2C transaction follows this pattern:
START 7-bit addr R/W ACK Data byte ACK Data byte ACK STOP
│ │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
SDA: ──╲ [A6..A0] [0] [0] [D7..D0] [0] [D7..D0] [0] ╱──
SCL: ───╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╱╲───
S P
- START (S): SDA falls while SCL is high
- Address: 7 bits identifying the slave (e.g., MCP9808 = 0x18)
- R/W bit: 0 = write, 1 = read
- ACK: Slave pulls SDA low to acknowledge (NACK = no response = device not present)
- Data: 8 bits per byte, MSB first
- STOP (P): SDA rises while SCL is high
Register read (common pattern for sensors):
1. Write: START → addr+W → register_addr → STOP
2. Read: START → addr+R → data_byte(s) → NACK → STOP
This is called a "repeated start" — the master doesn't release the bus between write and read.
2.3 SMBus vs I2C
SMBus (System Management Bus) is a stricter subset of I2C used by most Linux sensor drivers:
| I2C | SMBus | |
|---|---|---|
| Timeout | None (can hang forever) | 35 ms (bus released on timeout) |
| Max speed | 3.4 MHz | 100 kHz |
| Address range | 0x08–0x77 | 0x08–0x77 (same) |
| Defined transactions | Arbitrary | Block read/write, word read/write, byte read/write |
| Linux API | i2c_transfer() (raw) |
i2c_smbus_read_word_data() (structured) |
Most sensor drivers use the SMBus API because it's simpler and more portable:
/* Read 16-bit temperature from MCP9808 register 0x05 */
int raw = i2c_smbus_read_word_swapped(client, 0x05);
float temp = (raw & 0x0FFF) / 16.0f;
if (raw & 0x1000) temp -= 256.0f;
2.4 Linux I2C Subsystem
User space:
/dev/i2c-N ← raw I2C access (i2c-tools, Python smbus)
Kernel:
┌─────────────────────────────────────────────┐
│ I2C core (drivers/i2c/i2c-core.c) │
│ ├── i2c_adapter (bus controller) │
│ ├── i2c_client (device on the bus) │
│ └── i2c_driver (device driver) │
├─────────────────────────────────────────────┤
│ Adapter drivers (per SoC): │
│ bcm2835-i2c (Pi), i2c-designware (Intel) │
├─────────────────────────────────────────────┤
│ Device drivers (per chip): │
│ mcp9808, bmp280, ssd1306, ... │
└─────────────────────────────────────────────┘
Hardware:
BCM2835 BSC (Broadcom Serial Controller)
Registers at 0x7E804000 (I2C1), 0x7E805000 (I2C0)
2.5 Device Tree for I2C
Enable I2C bus:
/* In config.txt (Pi-specific shorthand): */
dtparam=i2c_arm=on
/* Equivalent DT overlay: enables i2c1 on GPIO 2 (SDA) + GPIO 3 (SCL) */
Add a device:
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <&i2c1>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
mcp9808@18 {
compatible = "microchip,mcp9808";
reg = <0x18>; /* 7-bit I2C address */
/* Optional: interrupt pin */
/* interrupt-parent = <&gpio>; */
/* interrupts = <4 2>; GPIO4, falling edge */
};
};
};
};
Key properties:
- compatible — matches the kernel driver's of_match_table
- reg — the 7-bit I2C slave address (find with i2cdetect -y 1)
- status = "okay" — enables this node (overrides "disabled" from base DT)
Compile and load:
dtc -@ -I dts -O dtb -o mcp9808.dtbo mcp9808-overlay.dts
sudo cp mcp9808.dtbo /boot/overlays/
# Add to config.txt: dtoverlay=mcp9808
2.6 Debugging I2C
# Scan bus for devices
i2cdetect -y 1
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
# 00: -- -- -- -- -- -- -- -- -- -- -- -- --
# 10: -- -- -- -- -- -- -- -- 18 -- -- -- -- -- -- --
# ↑ MCP9808 at 0x18
# Read register 0x05 from device 0x18
i2cget -y 1 0x18 0x05 w
# Write 0x00 to register 0x01
i2cset -y 1 0x18 0x01 0x00
# Dump all registers
i2cdump -y 1 0x18
Common problems:
| Symptom | Cause | Fix |
|---|---|---|
i2cdetect shows no devices |
Bus not enabled, wrong bus number | Check dtparam=i2c_arm=on, use -y 1 (not 0) |
Address shows UU |
Kernel driver already bound | Device is working — access via driver, not i2c-tools |
| Address changes randomly | Floating address pins | Tie A0/A1/A2 to VCC or GND |
| Read returns 0xFF | Pull-ups missing or too high | Add 4.7kΩ pull-ups (or rely on Pi's built-in) |
| Intermittent failures | Clock stretching + Pi bug | Pi 1-3 have a hardware I2C clock stretching bug; use dtparam=i2c_arm_baudrate=50000 to slow down |
3. SPI — Serial Peripheral Interface
3.1 Electrical Layer
SPI uses 4 signals (full-duplex, one CS per slave):
┌──────────┐ ┌───────────────┐
│ Master │ │ Slave 0 │
│ (Pi) │ │ (BMI160 IMU) │
│ MOSI ──────▶ DIN │
│ MISO ◀────── DOUT │
│ SCLK ──────▶ CLK │
│ CE0 ──────▶ CS (active low)│
└──────────┘ └───────────────┘
│ ┌───────────────┐
│ │ Slave 1 │
│ │ (SPI display) │
├── MOSI ─▶ DIN │
├── MISO ◀─ (not connected)│
├── SCLK ─▶ CLK │
└── CE1 ─▶ CS (active low)│
└───────────────┘
- MOSI (Master Out Slave In): data from master to slave
- MISO (Master In Slave Out): data from slave to master
- SCLK: clock generated by master
- CS/CE (Chip Select/Enable): active LOW, one per slave
No pull-ups needed — SPI uses push-pull drivers (not open-drain).
3.2 Clock Modes (CPOL/CPHA)
SPI has 4 clock modes defined by polarity (CPOL) and phase (CPHA):
Mode 0 (CPOL=0, CPHA=0) — most common:
SCLK: ──┐ ┌─┐ ┌─┐ ┌─┐ ┌──
└─┘ └─┘ └─┘ └─┘
DATA: ──X───X───X───X───X──
▲ ▲ ▲ ▲
Sample on rising edge
Mode 1 (CPOL=0, CPHA=1):
SCLK: ──┐ ┌─┐ ┌─┐ ┌─┐ ┌──
└─┘ └─┘ └─┘ └─┘
DATA: ────X───X───X───X────
▲ ▲ ▲ ▲
Sample on falling edge
Mode 2 (CPOL=1, CPHA=0):
SCLK: ┌─┐ ┌─┐ ┌─┐ ┌─┐
│ └─┘ └─┘ └─┘ └─┘
DATA: ──X───X───X───X───X──
▲ ▲ ▲ ▲
Sample on falling edge
Mode 3 (CPOL=1, CPHA=1):
SCLK: ┌─┐ ┌─┐ ┌─┐ ┌─┐
│ └─┘ └─┘ └─┘ └─┘
DATA: ────X───X───X───X────
▲ ▲ ▲ ▲
Sample on rising edge
The device datasheet tells you which mode to use. BMI160 uses Mode 0 or 3. Most SPI flash and displays use Mode 0.
Warning
Wrong clock mode = garbage data. If reads return 0xFF or random values, check CPOL/CPHA first. The Pi defaults to Mode 0.
3.3 SPI Register Access Pattern
Most SPI sensors use this convention:
Read register 0x40:
Master sends: [0x80 | 0x40] [0x00] ← bit 7 = read flag
Slave returns: [----] [data] ← first byte is dummy
Write register 0x40 = 0x05:
Master sends: [0x00 | 0x40] [0x05] ← bit 7 = 0 (write)
Slave returns: [----] [----] ← ignored
In Linux:
/* Read register */
uint8_t tx[2] = { 0x80 | reg, 0x00 };
uint8_t rx[2] = { 0 };
struct spi_transfer xfer = {
.tx_buf = tx, .rx_buf = rx, .len = 2
};
spi_sync_transfer(spi, &xfer, 1);
return rx[1]; /* data is in second byte */
3.4 Linux SPI Subsystem
User space:
/dev/spidevN.M ← raw SPI access (spidev driver)
Kernel:
┌─────────────────────────────────────────────┐
│ SPI core (drivers/spi/spi.c) │
│ ├── spi_controller (bus controller) │
│ ├── spi_device (device on the bus) │
│ └── spi_driver (device driver) │
├─────────────────────────────────────────────┤
│ Controller drivers (per SoC): │
│ spi-bcm2835 (Pi), spi-sun6i (Allwinner) │
├─────────────────────────────────────────────┤
│ Device drivers (per chip): │
│ bmi160_spi, ili9341 (display), spidev │
└─────────────────────────────────────────────┘
3.5 Device Tree for SPI
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <&spi0>;
__overlay__ {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
bmi160@0 {
compatible = "bosch,bmi160";
reg = <0>; /* CS0 (CE0) */
spi-max-frequency = <10000000>; /* 10 MHz */
spi-cpol; /* CPOL=1 (Mode 2 or 3) */
spi-cpha; /* CPHA=1 (Mode 3) */
/* interrupt-parent = <&gpio>; */
/* interrupts = <25 1>; GPIO25, rising edge */
};
};
};
};
Key properties:
- reg — CS line number (0 = CE0, 1 = CE1 on Pi)
- spi-max-frequency — maximum clock in Hz (device-specific)
- spi-cpol / spi-cpha — clock polarity/phase (omit both for Mode 0)
3.6 SPI Speed and Throughput
The Pi's SPI controller can run up to ~125 MHz, but practical limits:
| Device | Typical max | Notes |
|---|---|---|
| BMI160 IMU | 10 MHz | Datasheet limit |
| SSD1351 OLED | 20 MHz | Display refresh ~30 fps |
| ILI9341 TFT | 32 MHz | 320×240 @ 30 fps needs ~36 Mbps |
| SPI flash (W25Q) | 80 MHz | Bulk transfers, DMA helps |
Throughput calculation:
Raw: 10 MHz clock × 1 bit/clock = 10 Mbps = 1.25 MB/s
Overhead: ~20% (CS setup, inter-byte gaps, kernel overhead)
Effective: ~1 MB/s at 10 MHz
For display refresh at 320×240×16bpp×30fps = 36.9 Mbps → need ≥ 40 MHz SPI clock. See SPI DMA Optimization for achieving this.
4. I2S — Inter-IC Sound
4.1 Electrical Layer
I2S is designed specifically for digital audio between chips:
┌──────────┐ ┌───────────────┐
│ Master │ │ INMP441 Mic │
│ (Pi) │ │ │
│ BCLK ──────▶ SCK (bit clk) │
│ LRCLK ─────▶ WS (word sel) │
│ DIN ◀────── SD (data out) │
└──────────┘ │ L/R ← GND=L │
└───────────────┘
- BCLK (Bit Clock): clocks each data bit, generated by master
- LRCLK (Left/Right Clock, also called WS — Word Select): toggles between left and right channel. Frequency = sample rate
- DOUT/SD (Serial Data): audio samples, MSB first
4.2 Frame Format
The standard I2S frame for 24-bit audio at 48 kHz:
LRCLK: ────────┐ ┌──────────
│ LEFT CHANNEL │ RIGHT CHANNEL
└──────────────────────────────┘
BCLK: ─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─ ... ─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─
└─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘
DOUT: ──[B23][B22][B21]...[B0][0][0][0][0][B23][B22]...
▲ ▲
MSB first Right channel starts
1 BCLK delay after on LRCLK rising edge
LRCLK transition
Standard I2S has a 1-bit delay: data starts one BCLK after the LRCLK transition. This distinguishes it from left-justified format (no delay) and right-justified format (data aligned to end of slot).
4.3 Clock Relationships
The three clocks are mathematically related:
LRCLK = sample_rate (e.g., 48000 Hz)
BCLK = sample_rate × bits_per_slot × 2 (e.g., 48000 × 32 × 2 = 3.072 MHz)
MCLK = sample_rate × 256 (e.g., 48000 × 256 = 12.288 MHz)
(optional master clock, some codecs need it)
| Sample rate | Bits/slot | BCLK | MCLK (×256) |
|---|---|---|---|
| 8 kHz | 32 | 512 kHz | 2.048 MHz |
| 16 kHz | 32 | 1.024 MHz | 4.096 MHz |
| 44.1 kHz | 32 | 2.822 MHz | 11.289 MHz |
| 48 kHz | 32 | 3.072 MHz | 12.288 MHz |
| 96 kHz | 32 | 6.144 MHz | 24.576 MHz |
Why 32-Bit Slots for 24-Bit Audio?
The INMP441 microphone outputs 24-bit samples in a 32-bit slot (the lower 8 bits are zero). This is standard — I2S slots are always a power of 2 (16 or 32 bits). The 24-bit data is MSB-aligned within the 32-bit word.
In ALSA, this appears as S32_LE (signed 32-bit little-endian) format. The driver reads 32 bits per sample, and the application divides by 2^31 to normalize to float:
4.4 Raspberry Pi I2S Configuration
The Pi's BCM2835/2711 has one I2S interface (active PCM pins):
| Signal | Pi GPIO | Pin # | Function |
|---|---|---|---|
| BCLK | GPIO 18 | 12 | PCM_CLK |
| LRCLK | GPIO 19 | 35 | PCM_FS |
| DOUT (mic → Pi) | GPIO 20 | 38 | PCM_DIN |
| DIN (Pi → DAC) | GPIO 21 | 40 | PCM_DOUT |
Device Tree overlay for INMP441:
The simplest approach uses the generic simple-audio-card framework:
This overlay (provided by the course repo or Raspberry Pi OS) does:
- Enables the
bcm2835-i2scontroller - Creates an ALSA sound card with the
snd-simple-carddriver - Configures the codec as a "dummy" (the mic is too simple for a real codec driver)
If the standard overlay isn't available, create a custom one:
/dts-v1/;
/plugin/;
/ {
compatible = "brcm,bcm2835";
fragment@0 {
target = <&i2s_clk_producer>;
__overlay__ {
status = "okay";
};
};
fragment@1 {
target-path = "/";
__overlay__ {
mic_codec: simple-codec {
compatible = "invensense,inmp441", "linux,spdif-dit";
#sound-dai-cells = <0>;
status = "okay";
};
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "I2S-Mic";
simple-audio-card,format = "i2s";
simple-audio-card,cpu {
sound-dai = <&i2s_clk_producer>;
};
simple-audio-card,codec {
sound-dai = <&mic_codec>;
};
};
};
};
};
Key DT properties:
- simple-audio-card,format = "i2s" — standard I2S format (1-bit delay)
- The Pi generates BCLK and LRCLK as master; the mic is slave
- The "codec" is a dummy — INMP441 is a digital mic with no configuration registers
4.5 Linux ALSA Subsystem for I2S
User space:
arecord, aplay ← command-line tools
snd_pcm_open() ← ALSA library (libasound)
/dev/snd/pcmC*D*c ← capture device nodes
/dev/snd/pcmC*D*p ← playback device nodes
Kernel:
┌─────────────────────────────────────────────┐
│ ALSA core (sound/core/) │
│ ├── snd_card (sound card) │
│ ├── snd_pcm (PCM stream) │
│ └── snd_pcm_hw (hardware params) │
├─────────────────────────────────────────────┤
│ ASoC (ALSA System-on-Chip): │
│ ├── Platform driver (bcm2835-i2s) │
│ ├── Codec driver (dummy or real) │
│ └── Machine driver (simple-audio-card) │
└─────────────────────────────────────────────┘
Hardware:
BCM2835 PCM/I2S controller
Registers at 0x7E203000
DMA channels for capture and playback
ASoC (ALSA System-on-Chip) splits the audio path into three components:
- Platform (CPU-side DMA + I2S controller): bcm2835-i2s
- Codec (the audio chip): snd-soc-spdif-dit for dummy codecs, or real codec drivers (e.g., wm8960, pcm5102a)
- Machine (connects platform + codec): simple-audio-card or a board-specific driver
4.6 ALSA Configuration for Low Latency
The ALSA parameters that affect latency:
/* Period: smallest unit of transfer. ALSA wakes the app every period. */
snd_pcm_hw_params_set_period_size_near(pcm, hw, &period_frames, NULL);
/* Buffer: total ALSA buffer = N × period_size. Larger = more latency but fewer underruns. */
snd_pcm_hw_params_set_periods_near(pcm, hw, &num_periods, NULL);
/* Format: S32_LE for INMP441 (24-bit data in 32-bit slot) */
snd_pcm_hw_params_set_format(pcm, hw, SND_PCM_FORMAT_S32_LE);
| Period | Buffer (3 periods) | Latency | Underrun risk |
|---|---|---|---|
| 1024 frames | 3072 frames | 64 ms | Low |
| 512 frames | 1536 frames | 32 ms | Low |
| 256 frames | 768 frames | 16 ms | Medium |
| 128 frames | 384 frames | 8 ms | High (needs RT scheduling) |
See Audio Pipeline Latency for hands-on measurement.
4.7 Debugging I2S
# Check if the overlay loaded
dmesg | grep -i i2s
# → bcm2835-i2s 3f203000.i2s: ...
# List ALSA devices
arecord -l
# → card 1: sndrpisimplecar [snd_rpi_simple_card], device 0: ...
# Test capture (5 seconds, 48 kHz, stereo, S32_LE)
arecord -D hw:1,0 -f S32_LE -r 48000 -c 2 -d 5 test.wav
# Check with audio_viz_full
./audio_viz_full -D # list all ALSA devices
./audio_viz_full -d hw:1,0 -c 2 -f
Common problems:
| Symptom | Cause | Fix |
|---|---|---|
arecord -l shows no card |
Overlay not loaded | Add dtoverlay=i2s-mems-mic to config.txt, reboot |
| Captures silence | L/R pin wrong | INMP441: L/R to GND = left channel, to VCC = right. Try both -c 1 and -c 2 |
| Captures noise only | BCLK/LRCLK swapped | Check wiring: BCLK=GPIO18, LRCLK=GPIO19, DOUT=GPIO20 |
| Very quiet signal | 24-bit in 32-bit slot | Normal — apply software gain (4x–32x). See Audio Visualizer |
| Clicks/pops | Buffer underrun | Increase period size or enable SCHED_FIFO |
5. Bus Comparison for Design Decisions
When designing an embedded system, the bus choice depends on the device requirements:
| Criterion | I2C | SPI | I2S |
|---|---|---|---|
| Wiring complexity | Lowest (2 wires for all devices) | Medium (4 + 1 CS per device) | Low (3 wires) |
| Speed | Slow (≤ 3.4 MHz) | Fast (≤ 125 MHz) | Fixed (by sample rate) |
| CPU overhead | Low (small transfers) | Low-medium (DMA for bulk) | Low (DMA-driven) |
| Multi-device | Easy (addresses) | Harder (CS lines) | TDM (complex) |
| Hot-pluggable | Yes (with address scan) | No (CS must be wired) | No |
| Power | Low (can clock-gate) | Medium (always clocking) | Low (idle = no clock) |
| Debugging | Easy (i2cdetect) |
Harder (need scope) | Medium (arecord) |
| Typical devices | Temperature, humidity, RTC, EEPROM | IMU, display, flash, ADC | Mic, DAC, audio codec |
Mixed-Bus Design Example (This Course's Pi Setup)
Raspberry Pi 4
┌──────────────────────────────┐
I2C1 (100 kHz) ────┤ GPIO 2 (SDA), GPIO 3 (SCL) │
├── MCP9808 temp │ │
└── SSD1306 OLED │ │
│ │
SPI0 (10 MHz) ─────┤ GPIO 10/9/11 (MOSI/MISO/CLK)│
├── BMI160 IMU │ GPIO 8 (CE0) │
└── ILI9341 TFT │ GPIO 7 (CE1) │
│ │
I2S (3.072 MHz) ───┤ GPIO 18/19/20 (BCLK/LR/DIN) │
├── INMP441 mic L │ │
└── INMP441 mic R │ │
│ │
HDMI ──────────────┤ (display output) │
USB ───────────────┤ (keyboard, mouse) │
Ethernet ──────────┤ (SSH, network) │
└──────────────────────────────┘
I2C for slow sensors (temperature reads 2 bytes every second), SPI for fast data (IMU at 200 Hz, display at 30 fps), I2S for continuous audio (48 kHz stereo).
Further Reading
Specifications: - I2C-bus specification (NXP) — the official spec - SPI overview (Motorola/NXP) — original Motorola spec - I2S bus specification (Philips) — the original 1986 spec
Datasheets (course devices): - MCP9808 — I2C temperature sensor - BMI160 — SPI/I2C 6-axis IMU - INMP441 — I2S MEMS microphone
Linux kernel docs: - I2C subsystem — writing I2C drivers - SPI subsystem — writing SPI drivers - ASoC (ALSA SoC) — audio codec/platform drivers
Course tutorials: - Enable I2C — setup and verification - MCP9808 I2C Driver — write a kernel driver from scratch - BMI160 SPI Driver — SPI kernel driver with IIO - I2S Audio Visualizer — capture and process I2S audio - SPI Display — drive a TFT display over SPI - SPI DMA Optimization — bulk transfers with DMA - Audio Pipeline Latency — measure I2S capture-to-playback latency