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-haltraits provide a portable interface across all these peripherals.