5 min read
On this page

Peripherals and Interfaces

GPIO (General-Purpose Input/Output)

GPIO pins are the most basic peripheral -- each pin can be configured as digital input or output.

Pin Modes

Mode Description
Input floating High-impedance, reads external logic level
Input pull-up Internal resistor pulls to VCC when unconnected
Input pull-down Internal resistor pulls to GND when unconnected
Output push-pull Drives both high and low actively
Output open-drain Drives low actively, floats high (needs external pull-up)
Alternate function Pin controlled by a peripheral (UART TX, SPI CLK, etc.)
Analog Connected to ADC/DAC, digital buffer disabled

GPIO in Embedded Rust

dp ← TAKE_PERIPHERALS()
gpioa ← SPLIT_GPIO(dp.GPIOA)

// Output: LED on PA5
led ← CONFIGURE_PIN(gpioa.PA5, mode ← PUSH_PULL_OUTPUT)
SET_HIGH(led)

// Input: Button on PA0 with pull-up
button ← CONFIGURE_PIN(gpioa.PA0, mode ← PULL_UP_INPUT)
IF IS_LOW(button) THEN
    TOGGLE(led)

UART / USART

Universal Asynchronous Receiver-Transmitter. Full-duplex serial communication with two wires (TX, RX). USART adds synchronous mode with a clock line.

Frame Format

Idle ──┐   ┌─┬─┬─┬─┬─┬─┬─┬─┬──┬─┐
       │   │D│D│D│D│D│D│D│D│P │S│  Idle...
       └───┘0│1│2│3│4│5│6│7│  │ │
        Start        Data     Par Stop
  • Baud rate: 9600, 115200, etc. (bits per second)
  • Data bits: 7, 8, or 9
  • Parity: None, Even, Odd
  • Stop bits: 1 or 2

UART with embedded-hal

// Write a message
FOR EACH byte IN "Hello\r\n" DO
    UART_WRITE(uart, byte)    // blocking write

// Read a byte
received ← UART_READ(uart)    // blocking read

SPI (Serial Peripheral Interface)

Synchronous, full-duplex, master-slave protocol. Uses 4 wires:

Signal Direction Description
SCLK Master -> Slave Clock
MOSI Master -> Slave Master Out, Slave In
MISO Slave -> Master Master In, Slave Out
CS/SS Master -> Slave Chip Select (active low, one per slave)

SPI Clock Modes

Mode CPOL CPHA Clock Idle Sampling Edge
0 0 0 Low Rising
1 0 1 Low Falling
2 1 0 High Falling
3 1 1 High Rising

Multi-Slave Configuration

              SCLK ──────┬──────────┐
              MOSI ──────┤──────────┤
              MISO ──────┤──────────┤
Master        CS0  ──────┤ Slave 0  │
              CS1  ──────┤──────────┤ Slave 1
                         │          │

Each slave gets its own CS line. Only assert one CS at a time. Daisy-chaining is an alternative where MISO of one slave connects to MOSI of the next.

SPI in Rust

buf ← [0x9F, 0x00, 0x00, 0x00]   // Read JEDEC ID command
SPI_TRANSFER_IN_PLACE(spi_device, buf)
manufacturer_id ← buf[1]

I2C (Inter-Integrated Circuit)

Two-wire synchronous protocol with addressing. Uses open-drain lines with pull-up resistors.

Signal Description
SDA Serial Data (bidirectional)
SCL Serial Clock (master-driven)

I2C Transaction

  S | Addr(7) | R/W | ACK | Data(8) | ACK | Data(8) | ACK | P
  |   slave   | 0=W |     |         |     |         |     |
  Start       | 1=R |     |         |     |         |     Stop
  • 7-bit addressing: 112 usable addresses (0x08 - 0x77)
  • 10-bit addressing: extends to 1024 addresses (rare)
  • Standard mode: 100 kHz, Fast mode: 400 kHz, Fast mode+: 1 MHz

Clock Stretching

A slave can hold SCL low to pause the master when it needs more time to process data. The master must detect this and wait. Not all masters support clock stretching.

I2C in Rust

SENSOR_ADDR ← 0x48

// Write register address, then read 2 bytes
buf ← array of 2 bytes
I2C_WRITE_READ(i2c, SENSOR_ADDR, [0x00], buf)
temperature ← BYTES_TO_INT16_BIG_ENDIAN(buf)

ADC (Analog-to-Digital Converter)

Converts analog voltage to a digital value. Key parameters:

  • Resolution: 8, 10, 12, or 16 bits
  • Reference voltage (Vref): defines the full-scale range
  • Sampling time: how long the input is sampled before conversion
  • Conversion formula: voltage = (adc_value / 2^resolution) * Vref
adc ← ADC_INIT(ADC1, config ← defaults)
sample ← ADC_CONVERT(adc, adc_pin, sample_time ← 480 cycles)
voltage_mv ← (sample * 3300) / 4096   // 12-bit, 3.3V ref

DAC (Digital-to-Analog Converter)

Converts digital values to analog voltage. Used for audio output, waveform generation, and setting reference voltages. Typically 8-bit or 12-bit resolution.

PWM (Pulse Width Modulation)

Generates a square wave with variable duty cycle. Used for LED dimming, motor speed control, and servo positioning.

    |<--- Period (T) --->|
    +--------+           +--------+
    |        |           |        |
----+        +-----------+        +---
    |< ton  >|
    Duty = ton / T * 100%
pwm ← PWM_INIT(TIM3, pwm_pin, frequency ← 1 kHz, clocks)
SET_DUTY(pwm, GET_MAX_DUTY(pwm) / 2)   // 50% duty cycle
ENABLE(pwm)

Timers and Counters

Hardware timers are versatile peripherals used for:

  • Periodic interrupts: trigger code at fixed intervals
  • Input capture: measure pulse width or frequency of external signals
  • Output compare: generate precise timing events
  • PWM generation: hardware-driven waveform output
  • Encoder interface: read quadrature encoders for position tracking
  • One-pulse mode: generate a single pulse of precise duration

DMA (Direct Memory Access)

DMA transfers data between memory and peripherals without CPU intervention. The CPU sets up source, destination, and transfer count, then the DMA controller handles the transfer.

DMA Use Cases

Source Destination Application
ADC data register Memory buffer Continuous sensor sampling
Memory buffer UART data register Non-blocking serial TX
SPI data register Memory buffer High-speed SPI reads
Memory Memory Fast buffer copies

DMA channels can be configured for circular mode (auto-restart) or one-shot. Half-transfer and transfer-complete interrupts enable double-buffering patterns.

CAN Bus (Controller Area Network)

Multi-master, message-based protocol designed for automotive and industrial environments. Operates at up to 1 Mbps with built-in error detection and prioritization.

  • Differential signaling (CAN_H, CAN_L): noise-immune
  • Message-based: no addresses, each frame has an ID (11-bit or 29-bit)
  • Priority: lower ID = higher priority (bitwise arbitration)
  • Error handling: CRC, bit stuffing, ACK, error counters with bus-off recovery

CAN Frame

| SOF | ID (11-bit) | RTR | IDE | r0 | DLC (4) | Data (0-8 bytes) | CRC | ACK | EOF |

I2S (Inter-IC Sound)

Synchronous serial protocol for digital audio between MCUs, codecs, and DACs. Uses three wires: serial clock (SCK), word select (WS), and serial data (SD). Supports standard, left-justified, and right-justified formats.

USB (Universal Serial Bus)

MCUs can operate as USB device (most common) or USB host. Common device classes:

Class Use Case
CDC (Communications) Virtual COM port
HID (Human Interface) Keyboard, mouse, custom controls
MSC (Mass Storage) USB flash drive emulation
Audio USB microphone/speaker

USB requires careful clock configuration (48 MHz for USB Full Speed) and dedicated USB-capable pins.

Protocol Selection Guide

Protocol Wires Speed Topology Best For
UART 2 Up to ~1 Mbps Point-to-point Debug, GPS, modems
SPI 4+ Up to 50+ MHz Master + slaves Flash, displays, fast sensors
I2C 2 Up to 1 MHz Multi-master bus Sensors, EEPROMs, low pin count
CAN 2 (diff) Up to 1 Mbps Multi-master bus Automotive, industrial
USB 2 (diff) 12/480 Mbps Host-device tree PC connectivity

Key Takeaways

  • GPIO configuration (push-pull, open-drain, pull-up/down) must match the electrical circuit.
  • SPI is fastest but uses more pins; I2C uses fewer pins with addressing overhead.
  • ADC sampling time and resolution directly affect measurement accuracy and throughput.
  • DMA offloads repetitive data transfers from the CPU, critical for real-time performance.
  • The embedded-hal traits provide a portable interface across all these peripherals.